Compare commits

..

37 Commits

Author SHA1 Message Date
John Preston
a82d1e863e Version 2.7.1: Fix channels ban in admin log. 2021-03-20 18:19:31 +04:00
John Preston
26d97a3636 Version 2.7.1.
- Fix editing 'Manage Voice Chats' rights for channel admins.
- Fix verification check display in voice chat participants list.
- Allow removing and blocking channels from voice chats.
2021-03-20 16:23:41 +04:00
John Preston
7b8e421996 Allow markup in some voice chat toasts. 2021-03-20 16:23:41 +04:00
John Preston
2bc2a0e459 Fix possible integer overflow. 2021-03-20 16:03:58 +04:00
John Preston
7cb4b4f8ab Don't try to use WebRTC built-in audio backends. 2021-03-20 15:43:35 +04:00
John Preston
b439ecce16 Allow all toasts to be multiline in voice chats. 2021-03-20 15:43:35 +04:00
John Preston
a33a4c0589 Fix maximize/restore voice chat title bar button. 2021-03-20 15:43:35 +04:00
John Preston
5278e2201f Make red 'Remove' in voice chat participant menu. 2021-03-20 15:43:35 +04:00
John Preston
3bd6b2268f Allow blocking channels in voice chats. 2021-03-20 15:43:35 +04:00
John Preston
a0a13c3b86 Update API scheme to layer 126. 2021-03-20 15:43:35 +04:00
John Preston
0052c7938f Fix verified icon in voice chat participants list. 2021-03-20 15:43:35 +04:00
John Preston
a14db3e492 Allow editing 'Manage Voice Chats' admin right in channels. 2021-03-20 15:43:35 +04:00
Ilya Fedin
7979b3b6c8 Fix devtoolset version in linux action 2021-03-20 14:33:02 +03:00
Ilya Fedin
3f25e92afd Add build options for libtgvoip backends
libtgvoip has options to disbale some backends, but they never were exposed via tdesktop's cmake

Since libtgvoip autoconf build system doesn't work anymore, it's worth to have these options in tdesktop's cmake.
2021-03-20 14:33:02 +03:00
Ilya Fedin
3d1cddaca5 Add a way to change default handler in snap 2021-03-20 14:20:09 +03:00
John Preston
eeecc42c25 Version 2.7.
- Start limitless Voice Chats in Groups and Channels.
- Host discussions that can be listened to
by millions of people simultaneously.
- Record voice chats to share or publish in Channels later.
- See that a chat is being recorded
from the red dot next to its title.
- See user bio texts right from the list of participants.
- Raise your hand to show admins you want to speak.
- Create separate Voice Chat Invite Links for listeners or speakers.
- Change the title of your Voice Chat
to give people an idea of the current topic.
- Join Voice Chats as one of your Channels
to hide your personal account.
2021-03-19 14:15:26 +04:00
John Preston
e22ecafc1d Add confirmation on create / anonymous admin join. 2021-03-19 14:10:02 +04:00
John Preston
ba41da7b28 Fix discarded group call handle. 2021-03-19 00:57:16 +04:00
John Preston
9cfbccf9e7 Beta version 2.6.8.
- Fix connecting and getting allowed to speak on voice chats.
- MPRIS support on Linux.
2021-03-18 22:56:42 +04:00
John Preston
2b6f50e114 Fix joining / unmuting. 2021-03-18 22:56:42 +04:00
Ilya Fedin
d2f57b72c3 Add mpris permission for snap 2021-03-18 21:55:06 +03:00
Ilya Fedin
85ac983a27 Add MPRIS support 2021-03-18 21:55:06 +03:00
John Preston
ac397e6e19 Beta version 2.6.7.
- Improve voice chat participants list updating.
2021-03-18 18:05:43 +04:00
John Preston
38e15c9bdb Fix saving legacy chat admins without migration.
Fixes #10558.
2021-03-18 17:27:33 +04:00
John Preston
00d65fa978 Request one participants slice on voice chat reload. 2021-03-18 16:58:05 +04:00
John Preston
3fea9cca08 Subscribe to channel updates in voice chat. 2021-03-18 15:54:28 +04:00
John Preston
b390e0766b Apply all queued updates on reload. 2021-03-18 15:30:58 +04:00
John Preston
2f75e6bbe2 Add some logging for voice chat updates. 2021-03-18 15:22:55 +04:00
Ilya Fedin
decbbb9a73 Check for openal fork updates in Dockerfile 2021-03-18 07:51:57 +03:00
Ilya Fedin
b4b80822c8 Set glib's application name and prgname 2021-03-18 07:51:57 +03:00
John Preston
bc82cdc3b3 Call dump_syms and strip outside of docker. 2021-03-18 02:33:29 +04:00
John Preston
ebc67d25f0 Migrate docker build to GCC 9. 2021-03-18 00:30:12 +04:00
John Preston
348b4d54ba Revert "Workaround build issues on GCC."
This reverts commit 3defb06783.
2021-03-18 00:30:12 +04:00
John Preston
6f86ce595b Beta version 2.6.6.
- Fix joining popular voice chats.
2021-03-18 00:26:53 +04:00
John Preston
8c53a3c19e Don't skip updateGroupCallParticipants while in getDifference. 2021-03-18 00:25:53 +04:00
John Preston
67623072d6 Fix joining a voice chat. 2021-03-18 00:24:36 +04:00
John Preston
1291f1c80d Beta version 2.6.5.
- Improvements and fixes in new voice chat features.
2021-03-17 21:16:55 +04:00
60 changed files with 1881 additions and 759 deletions

View File

@@ -55,7 +55,7 @@ jobs:
defaults:
run:
shell: scl enable devtoolset-8 -- bash --noprofile --norc -eo pipefail {0}
shell: scl enable devtoolset-9 -- bash --noprofile --norc -eo pipefail {0}
strategy:
matrix:
@@ -65,6 +65,8 @@ jobs:
- "DESKTOP_APP_DISABLE_X11_INTEGRATION"
- "DESKTOP_APP_DISABLE_WAYLAND_INTEGRATION"
- "DESKTOP_APP_DISABLE_GTK_INTEGRATION"
- "LIBTGVOIP_DISABLE_ALSA"
- "LIBTGVOIP_DISABLE_PULSEAUDIO"
env:
UPLOAD_ARTIFACT: "false"

View File

@@ -821,6 +821,8 @@ PRIVATE
platform/linux/linux_gtk_integration.h
platform/linux/linux_gtk_open_with_dialog.cpp
platform/linux/linux_gtk_open_with_dialog.h
platform/linux/linux_mpris_support.cpp
platform/linux/linux_mpris_support.h
platform/linux/linux_notification_service_watcher.cpp
platform/linux/linux_notification_service_watcher.h
platform/linux/linux_wayland_integration.cpp
@@ -1113,6 +1115,8 @@ if (DESKTOP_APP_DISABLE_DBUS_INTEGRATION)
remove_target_sources(Telegram ${src_loc}
platform/linux/linux_gsd_media_keys.cpp
platform/linux/linux_gsd_media_keys.h
platform/linux/linux_mpris_support.cpp
platform/linux/linux_mpris_support.h
platform/linux/linux_notification_service_watcher.cpp
platform/linux/linux_notification_service_watcher.h
platform/linux/linux_xdp_file_dialog.cpp

View File

@@ -1939,6 +1939,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_group_call_leave_sure" = "Are you sure you want to leave this voice chat?";
"lng_group_call_leave_to_other_sure" = "Do you want to leave your active voice chat and join a voice chat in this group?";
"lng_group_call_create_sure" = "Do you really want to start a voice chat in this group?";
"lng_group_call_create_sure_channel" = "Are you sure you want to start a voice chat in this channel as your personal account?";
"lng_group_call_join_sure_personal" = "Are you sure you want to join this voice chat as your personal account?";
"lng_group_call_also_end" = "End voice chat";
"lng_group_call_settings_title" = "Settings";
"lng_group_call_invite" = "Invite Member";
@@ -1980,6 +1982,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_group_call_context_remove_hand" = "Cancel request to speak";
"lng_group_call_context_mute_for_me" = "Mute for me";
"lng_group_call_context_unmute_for_me" = "Unmute for me";
"lng_group_call_context_remove" = "Remove";
"lng_group_call_remove_channel" = "Remove {channel} from the voice chat?";
"lng_group_call_duration_days#one" = "{count} day";
"lng_group_call_duration_days#other" = "{count} days";
"lng_group_call_duration_hours#one" = "{count} hour";
@@ -2222,6 +2226,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_admin_log_stopped_poll" = "{from} stopped poll:";
"lng_admin_log_invited" = "invited {user}";
"lng_admin_log_banned" = "banned {user}";
"lng_admin_log_unbanned" = "unbanned {user}";
"lng_admin_log_restricted" = "changed restrictions for {user} {until}";
"lng_admin_log_promoted" = "changed privileges for {user}";
"lng_admin_log_transferred" = "transferred ownership to {user}";

View File

@@ -620,8 +620,8 @@ channelParticipant#15ebac1d user_id:int date:int = ChannelParticipant;
channelParticipantSelf#a3289a6d user_id:int inviter_id:int date:int = ChannelParticipant;
channelParticipantCreator#447dca4b flags:# user_id:int admin_rights:ChatAdminRights rank:flags.0?string = ChannelParticipant;
channelParticipantAdmin#ccbebbaf flags:# can_edit:flags.0?true self:flags.1?true user_id:int inviter_id:flags.1?int promoted_by:int date:int admin_rights:ChatAdminRights rank:flags.2?string = ChannelParticipant;
channelParticipantBanned#1c0facaf flags:# left:flags.0?true user_id:int kicked_by:int date:int banned_rights:ChatBannedRights = ChannelParticipant;
channelParticipantLeft#c3c6796b user_id:int = ChannelParticipant;
channelParticipantBanned#50a1dfd6 flags:# left:flags.0?true peer:Peer kicked_by:int date:int banned_rights:ChatBannedRights = ChannelParticipant;
channelParticipantLeft#1b03f006 peer:Peer = ChannelParticipant;
channelParticipantsRecent#de3f3c79 = ChannelParticipantsFilter;
channelParticipantsAdmins#b4608969 = ChannelParticipantsFilter;
@@ -632,10 +632,10 @@ channelParticipantsSearch#656ac4b q:string = ChannelParticipantsFilter;
channelParticipantsContacts#bb6ae88d q:string = ChannelParticipantsFilter;
channelParticipantsMentions#e04b5ceb flags:# q:flags.0?string top_msg_id:flags.1?int = ChannelParticipantsFilter;
channels.channelParticipants#f56ee2a8 count:int participants:Vector<ChannelParticipant> users:Vector<User> = channels.ChannelParticipants;
channels.channelParticipants#9ab0feaf count:int participants:Vector<ChannelParticipant> chats:Vector<Chat> users:Vector<User> = channels.ChannelParticipants;
channels.channelParticipantsNotModified#f0173fe9 = channels.ChannelParticipants;
channels.channelParticipant#d0d9b163 participant:ChannelParticipant users:Vector<User> = channels.ChannelParticipant;
channels.channelParticipant#dfb80317 participant:ChannelParticipant chats:Vector<Chat> users:Vector<User> = channels.ChannelParticipant;
help.termsOfService#780a0310 flags:# popup:flags.0?true id:DataJSON text:string entities:Vector<MessageEntity> min_age_confirm:flags.1?int = help.TermsOfService;
@@ -1557,7 +1557,7 @@ channels.deleteUserHistory#d10dd71b channel:InputChannel user_id:InputUser = mes
channels.reportSpam#fe087810 channel:InputChannel user_id:InputUser id:Vector<int> = Bool;
channels.getMessages#ad8c9a23 channel:InputChannel id:Vector<InputMessage> = messages.Messages;
channels.getParticipants#123e05e9 channel:InputChannel filter:ChannelParticipantsFilter offset:int limit:int hash:int = channels.ChannelParticipants;
channels.getParticipant#546dd7a6 channel:InputChannel user_id:InputUser = channels.ChannelParticipant;
channels.getParticipant#a0ab6cc6 channel:InputChannel participant:InputPeer = channels.ChannelParticipant;
channels.getChannels#a7f6bbb id:Vector<InputChannel> = messages.Chats;
channels.getFullChannel#8736a09 channel:InputChannel = messages.ChatFull;
channels.createChannel#3d5fb10f flags:# broadcast:flags.0?true megagroup:flags.1?true for_import:flags.3?true title:string about:string geo_point:flags.2?InputGeoPoint address:flags.2?string = Updates;
@@ -1573,7 +1573,7 @@ channels.deleteChannel#c0111fe3 channel:InputChannel = Updates;
channels.exportMessageLink#e63fadeb flags:# grouped:flags.0?true thread:flags.1?true channel:InputChannel id:int = ExportedMessageLink;
channels.toggleSignatures#1f69b606 channel:InputChannel enabled:Bool = Updates;
channels.getAdminedPublicChannels#f8b036af flags:# by_location:flags.0?true check_limit:flags.1?true = messages.Chats;
channels.editBanned#72796912 channel:InputChannel user_id:InputUser banned_rights:ChatBannedRights = Updates;
channels.editBanned#96e6cd81 channel:InputChannel participant:InputPeer banned_rights:ChatBannedRights = Updates;
channels.getAdminLog#33ddf480 flags:# channel:InputChannel q:string events_filter:flags.0?ChannelAdminLogEventsFilter admins:flags.1?Vector<InputUser> max_id:long min_id:long limit:int = channels.AdminLogResults;
channels.setStickers#ea8ca4f9 channel:InputChannel stickerset:InputStickerSet = Bool;
channels.readMessageContents#eab5dc38 channel:InputChannel id:Vector<int> = Bool;
@@ -1645,4 +1645,4 @@ stats.getMegagroupStats#dcdf8607 flags:# dark:flags.0?true channel:InputChannel
stats.getMessagePublicForwards#5630281b channel:InputChannel msg_id:int offset_rate:int offset_peer:InputPeer offset_id:int limit:int = messages.Messages;
stats.getMessageStats#b6e0a3f5 flags:# dark:flags.0?true channel:InputChannel msg_id:int = stats.MessageStats;
// LAYER 125
// LAYER 126

View File

@@ -9,7 +9,7 @@
<Identity Name="TelegramMessengerLLP.TelegramDesktop"
ProcessorArchitecture="ARCHITECTURE"
Publisher="CN=536BC709-8EE1-4478-AF22-F0F0F26FF64A"
Version="2.6.4.0" />
Version="2.7.1.0" />
<Properties>
<DisplayName>Telegram Desktop</DisplayName>
<PublisherDisplayName>Telegram Messenger LLP</PublisherDisplayName>

View File

@@ -44,8 +44,8 @@ IDI_ICON1 ICON "..\\art\\icon256.ico"
//
VS_VERSION_INFO VERSIONINFO
FILEVERSION 2,6,4,0
PRODUCTVERSION 2,6,4,0
FILEVERSION 2,7,1,0
PRODUCTVERSION 2,7,1,0
FILEFLAGSMASK 0x3fL
#ifdef _DEBUG
FILEFLAGS 0x1L
@@ -62,10 +62,10 @@ BEGIN
BEGIN
VALUE "CompanyName", "Telegram FZ-LLC"
VALUE "FileDescription", "Telegram Desktop"
VALUE "FileVersion", "2.6.4.0"
VALUE "FileVersion", "2.7.1.0"
VALUE "LegalCopyright", "Copyright (C) 2014-2021"
VALUE "ProductName", "Telegram Desktop"
VALUE "ProductVersion", "2.6.4.0"
VALUE "ProductVersion", "2.7.1.0"
END
END
BLOCK "VarFileInfo"

View File

@@ -35,8 +35,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
//
VS_VERSION_INFO VERSIONINFO
FILEVERSION 2,6,4,0
PRODUCTVERSION 2,6,4,0
FILEVERSION 2,7,1,0
PRODUCTVERSION 2,7,1,0
FILEFLAGSMASK 0x3fL
#ifdef _DEBUG
FILEFLAGS 0x1L
@@ -53,10 +53,10 @@ BEGIN
BEGIN
VALUE "CompanyName", "Telegram FZ-LLC"
VALUE "FileDescription", "Telegram Desktop Updater"
VALUE "FileVersion", "2.6.4.0"
VALUE "FileVersion", "2.7.1.0"
VALUE "LegalCopyright", "Copyright (C) 2014-2021"
VALUE "ProductName", "Telegram Desktop"
VALUE "ProductVersion", "2.6.4.0"
VALUE "ProductVersion", "2.7.1.0"
END
END
BLOCK "VarFileInfo"

View File

@@ -277,13 +277,13 @@ void Updates::checkLastUpdate(bool afterSleep) {
void Updates::feedUpdateVector(
const MTPVector<MTPUpdate> &updates,
bool skipMessageIds) {
SkipUpdatePolicy policy) {
auto list = updates.v;
const auto needsSorting = ranges::contains(
const auto hasGroupCallParticipantUpdates = ranges::contains(
list,
mtpc_updateGroupCallParticipants,
&MTPUpdate::type);
if (needsSorting) {
if (hasGroupCallParticipantUpdates) {
ranges::stable_sort(list, std::less<>(), [](const MTPUpdate &entry) {
if (entry.type() == mtpc_updateGroupCallParticipants) {
return 0;
@@ -291,9 +291,15 @@ void Updates::feedUpdateVector(
return 1;
}
});
} else if (policy == SkipUpdatePolicy::SkipExceptGroupCallParticipants) {
return;
}
for (const auto &entry : std::as_const(list)) {
if (skipMessageIds && entry.type() == mtpc_updateMessageID) {
const auto type = entry.type();
if ((policy == SkipUpdatePolicy::SkipMessageIds
&& type == mtpc_updateMessageID)
|| (policy == SkipUpdatePolicy::SkipExceptGroupCallParticipants
&& type != mtpc_updateGroupCallParticipants)) {
continue;
}
feedUpdate(entry);
@@ -406,7 +412,9 @@ void Updates::feedChannelDifference(
session().data().processMessages(
data.vnew_messages(),
NewMessageType::Unread);
feedUpdateVector(data.vother_updates(), true);
feedUpdateVector(
data.vother_updates(),
SkipUpdatePolicy::SkipMessageIds);
_handlingChannelDifference = false;
}
@@ -567,7 +575,7 @@ void Updates::feedDifference(
session().data().processChats(chats);
feedMessageIds(other);
session().data().processMessages(msgs, NewMessageType::Unread);
feedUpdateVector(other, true);
feedUpdateVector(other, SkipUpdatePolicy::SkipMessageIds);
}
void Updates::differenceFail(const MTP::Error &error) {
@@ -824,9 +832,32 @@ void Updates::mtpUpdateReceived(const MTPUpdates &updates) {
if (!requestingDifference()
|| HasForceLogoutNotification(updates)) {
applyUpdates(updates);
} else {
applyGroupCallParticipantUpdates(updates);
}
}
void Updates::applyGroupCallParticipantUpdates(const MTPUpdates &updates) {
updates.match([&](const MTPDupdates &data) {
session().data().processUsers(data.vusers());
session().data().processChats(data.vchats());
feedUpdateVector(
data.vupdates(),
SkipUpdatePolicy::SkipExceptGroupCallParticipants);
}, [&](const MTPDupdatesCombined &data) {
session().data().processUsers(data.vusers());
session().data().processChats(data.vchats());
feedUpdateVector(
data.vupdates(),
SkipUpdatePolicy::SkipExceptGroupCallParticipants);
}, [&](const MTPDupdateShort &data) {
if (data.vupdate().type() == mtpc_updateGroupCallParticipants) {
feedUpdate(data.vupdate());
}
}, [](const auto &) {
});
}
int32 Updates::pts() const {
return _ptsWaiter.current();
}

View File

@@ -66,6 +66,12 @@ private:
AfterFail,
};
enum class SkipUpdatePolicy {
SkipNone,
SkipMessageIds,
SkipExceptGroupCallParticipants,
};
struct ActiveChatTracker {
PeerData *peer = nullptr;
rpl::lifetime lifetime;
@@ -113,12 +119,14 @@ private:
void mtpNewSessionCreated();
void feedUpdateVector(
const MTPVector<MTPUpdate> &updates,
bool skipMessageIds = false);
SkipUpdatePolicy policy = SkipUpdatePolicy::SkipNone);
// Doesn't call sendHistoryChangeNotifications itself.
void feedMessageIds(const MTPVector<MTPUpdate> &updates);
// Doesn't call sendHistoryChangeNotifications itself.
void feedUpdate(const MTPUpdate &update);
void applyGroupCallParticipantUpdates(const MTPUpdates &updates);
bool whenGetDiffChanged(
ChannelData *channel,
int32 ms,

View File

@@ -1522,13 +1522,20 @@ void ApiWrap::applyLastParticipantsList(
auto botStatus = channel->mgInfo->botStatus;
const auto emptyAdminRights = MTP_chatAdminRights(MTP_flags(0));
const auto emptyRestrictedRights = MTP_chatBannedRights(
MTP_flags(0),
MTP_int(0));
for (const auto &p : list) {
const auto userId = p.match([](const auto &data) {
return data.vuser_id().v;
const auto participantId = p.match([](
const MTPDchannelParticipantBanned &data) {
return peerFromMTP(data.vpeer());
}, [](const MTPDchannelParticipantLeft &data) {
return peerFromMTP(data.vpeer());
}, [](const auto &data) {
return peerFromUser(data.vuser_id());
});
if (!participantId) {
continue;
}
const auto participant = _session->data().peer(participantId);
const auto user = participant->asUser();
const auto adminCanEdit = (p.type() == mtpc_channelParticipantAdmin)
? p.c_channelParticipantAdmin().is_can_edit()
: (p.type() == mtpc_channelParticipantCreator)
@@ -1541,28 +1548,27 @@ void ApiWrap::applyLastParticipantsList(
: emptyAdminRights;
const auto restrictedRights = (p.type() == mtpc_channelParticipantBanned)
? p.c_channelParticipantBanned().vbanned_rights()
: emptyRestrictedRights;
if (!userId) {
continue;
}
auto user = _session->data().user(userId);
: ChannelData::EmptyRestrictedRights(participant);
if (p.type() == mtpc_channelParticipantCreator) {
Assert(user != nullptr);
const auto &creator = p.c_channelParticipantCreator();
const auto rank = qs(creator.vrank().value_or_empty());
channel->mgInfo->creator = user;
channel->mgInfo->creatorRank = rank;
if (!channel->mgInfo->admins.empty()) {
Data::ChannelAdminChanges(channel).add(userId, rank);
Data::ChannelAdminChanges(channel).add(
peerToUser(participantId),
rank);
}
}
if (!base::contains(channel->mgInfo->lastParticipants, user)) {
if (user
&& !base::contains(channel->mgInfo->lastParticipants, user)) {
channel->mgInfo->lastParticipants.push_back(user);
if (adminRights.c_chatAdminRights().vflags().v) {
channel->mgInfo->lastAdmins.emplace(
user,
MegagroupInfo::Admin{ adminRights, adminCanEdit });
} else if (restrictedRights.c_chatBannedRights().vflags().v != 0) {
} else if (Data::ChatBannedRightsFlags(restrictedRights) != 0) {
channel->mgInfo->lastRestricted.emplace(
user,
MegagroupInfo::Restricted{ restrictedRights });
@@ -1607,22 +1613,29 @@ void ApiWrap::applyBotsList(
auto botStatus = channel->mgInfo->botStatus;
auto keyboardBotFound = !history || !history->lastKeyboardFrom;
for (const auto &p : list) {
const auto userId = p.match([](const auto &data) {
return data.vuser_id().v;
const auto participantId = p.match([](
const MTPDchannelParticipantBanned &data) {
return peerFromMTP(data.vpeer());
}, [](const MTPDchannelParticipantLeft &data) {
return peerFromMTP(data.vpeer());
}, [](const auto &data) {
return peerFromUser(data.vuser_id());
});
if (!userId) {
if (!participantId) {
continue;
}
auto user = _session->data().user(userId);
if (user->isBot()) {
const auto participant = _session->data().peer(participantId);
const auto user = participant->asUser();
if (user && user->isBot()) {
channel->mgInfo->bots.insert(user);
botStatus = 2;// (botStatus > 0/* || !i.key()->botInfo->readsAllHistory*/) ? 2 : 1;
if (!user->botInfo->inited) {
needBotsInfos = true;
}
}
if (!keyboardBotFound && user->id == history->lastKeyboardFrom) {
if (!keyboardBotFound
&& participant->id == history->lastKeyboardFrom) {
keyboardBotFound = true;
}
}
@@ -1657,7 +1670,7 @@ void ApiWrap::requestSelfParticipant(not_null<ChannelData*> channel) {
_selfParticipantRequests.emplace(channel);
request(MTPchannels_GetParticipant(
channel->inputChannel,
MTP_inputUserSelf()
MTP_inputPeerSelf()
)).done([=](const MTPchannels_ChannelParticipant &result) {
_selfParticipantRequests.erase(channel);
result.match([&](const MTPDchannels_channelParticipant &data) {
@@ -1698,11 +1711,13 @@ void ApiWrap::requestSelfParticipant(not_null<ChannelData*> channel) {
void ApiWrap::kickParticipant(
not_null<ChatData*> chat,
not_null<UserData*> user) {
not_null<PeerData*> participant) {
Expects(participant->isUser());
request(MTPmessages_DeleteChatUser(
MTP_flags(0),
chat->inputChat,
user->inputUser
participant->asUser()->inputUser
)).done([=](const MTPUpdates &result) {
applyUpdates(result);
}).send();
@@ -1710,21 +1725,21 @@ void ApiWrap::kickParticipant(
void ApiWrap::kickParticipant(
not_null<ChannelData*> channel,
not_null<UserData*> user,
not_null<PeerData*> participant,
const MTPChatBannedRights &currentRights) {
const auto kick = KickRequest(channel, user);
const auto kick = KickRequest(channel, participant);
if (_kickRequests.contains(kick)) return;
const auto rights = ChannelData::KickedRestrictedRights();
const auto rights = ChannelData::KickedRestrictedRights(participant);
const auto requestId = request(MTPchannels_EditBanned(
channel->inputChannel,
user->inputUser,
participant->input,
rights
)).done([=](const MTPUpdates &result) {
applyUpdates(result);
_kickRequests.remove(KickRequest(channel, user));
channel->applyEditBanned(user, currentRights, rights);
_kickRequests.remove(KickRequest(channel, participant));
channel->applyEditBanned(participant, currentRights, rights);
}).fail([this, kick](const MTP::Error &error) {
_kickRequests.remove(kick);
}).send();
@@ -1734,20 +1749,20 @@ void ApiWrap::kickParticipant(
void ApiWrap::unblockParticipant(
not_null<ChannelData*> channel,
not_null<UserData*> user) {
const auto kick = KickRequest(channel, user);
not_null<PeerData*> participant) {
const auto kick = KickRequest(channel, participant);
if (_kickRequests.contains(kick)) {
return;
}
const auto requestId = request(MTPchannels_EditBanned(
channel->inputChannel,
user->inputUser,
MTP_chatBannedRights(MTP_flags(0), MTP_int(0))
participant->input,
ChannelData::EmptyRestrictedRights(participant)
)).done([=](const MTPUpdates &result) {
applyUpdates(result);
_kickRequests.remove(KickRequest(channel, user));
_kickRequests.remove(KickRequest(channel, participant));
if (channel->kickedCount() > 0) {
channel->setKickedCount(channel->kickedCount() - 1);
} else {
@@ -3183,12 +3198,20 @@ void ApiWrap::refreshChannelAdmins(
const QVector<MTPChannelParticipant> &participants) {
Data::ChannelAdminChanges changes(channel);
for (const auto &p : participants) {
const auto userId = p.match([](const auto &data) {
return data.vuser_id().v;
const auto participantId = p.match([](
const MTPDchannelParticipantBanned &data) {
return peerFromMTP(data.vpeer());
}, [](const MTPDchannelParticipantLeft &data) {
return peerFromMTP(data.vpeer());
}, [](const auto &data) {
return peerFromUser(data.vuser_id());
});
const auto userId = peerToUser(participantId);
p.match([&](const MTPDchannelParticipantAdmin &data) {
Assert(peerIsUser(participantId));
changes.add(userId, qs(data.vrank().value_or_empty()));
}, [&](const MTPDchannelParticipantCreator &data) {
Assert(peerIsUser(participantId));
const auto rank = qs(data.vrank().value_or_empty());
if (const auto info = channel->mgInfo.get()) {
info->creator = channel->owner().userLoaded(userId);
@@ -3196,7 +3219,9 @@ void ApiWrap::refreshChannelAdmins(
}
changes.add(userId, rank);
}, [&](const auto &data) {
changes.remove(userId);
if (userId) {
changes.remove(userId);
}
});
}
}

View File

@@ -249,14 +249,16 @@ public:
void markMediaRead(not_null<HistoryItem*> item);
void requestSelfParticipant(not_null<ChannelData*> channel);
void kickParticipant(not_null<ChatData*> chat, not_null<UserData*> user);
void kickParticipant(
not_null<ChatData*> chat,
not_null<PeerData*> participant);
void kickParticipant(
not_null<ChannelData*> channel,
not_null<UserData*> user,
not_null<PeerData*> participant,
const MTPChatBannedRights &currentRights);
void unblockParticipant(
not_null<ChannelData*> channel,
not_null<UserData*> user);
not_null<PeerData*> participant);
void deleteAllFromUser(
not_null<ChannelData*> channel,
not_null<UserData*> from);
@@ -657,7 +659,7 @@ private:
using KickRequest = std::pair<
not_null<ChannelData*>,
not_null<UserData*>>;
not_null<PeerData*>>;
base::flat_map<KickRequest, mtpRequestId> _kickRequests;
base::flat_set<not_null<ChannelData*>> _selfParticipantRequests;

View File

@@ -900,7 +900,7 @@ void DeleteMessagesBox::deleteAndClear() {
_moderateInChannel->session().api().kickParticipant(
_moderateInChannel,
_moderateFrom,
MTP_chatBannedRights(MTP_flags(0), MTP_int(0)));
ChannelData::EmptyRestrictedRights(_moderateFrom));
}
if (_reportSpam->checked()) {
_moderateInChannel->session().api().request(

View File

@@ -421,6 +421,7 @@ void AddSpecialBoxController::rebuildChatRows(not_null<ChatData*> chat) {
auto count = delegate()->peerListFullRowsCount();
for (auto i = 0; i != count;) {
auto row = delegate()->peerListRowAt(i);
Assert(row->peer()->isUser());
auto user = row->peer()->asUser();
if (participants.contains(user)) {
++i;
@@ -467,8 +468,9 @@ void AddSpecialBoxController::loadMoreRows() {
int availableCount,
const QVector<MTPChannelParticipant> &list) {
for (const auto &data : list) {
if (const auto user = _additional.applyParticipant(data)) {
appendRow(user);
if (const auto participant = _additional.applyParticipant(
data)) {
appendRow(participant);
}
}
if (const auto size = list.size()) {
@@ -491,20 +493,25 @@ void AddSpecialBoxController::loadMoreRows() {
}
void AddSpecialBoxController::rowClicked(not_null<PeerListRow*> row) {
auto user = row->peer()->asUser();
const auto participant = row->peer();
const auto user = participant->asUser();
switch (_role) {
case Role::Admins: return showAdmin(user);
case Role::Restricted: return showRestricted(user);
case Role::Kicked: return kickUser(user);
case Role::Admins:
Assert(user != nullptr);
return showAdmin(user);
case Role::Restricted:
Assert(user != nullptr);
return showRestricted(user);
case Role::Kicked: return kickUser(participant);
}
Unexpected("Role in AddSpecialBoxController::rowClicked()");
}
template <typename Callback>
bool AddSpecialBoxController::checkInfoLoaded(
not_null<UserData*> user,
not_null<PeerData*> participant,
Callback callback) {
if (_additional.infoLoaded(user)) {
if (_additional.infoLoaded(participant)) {
return true;
}
@@ -512,16 +519,15 @@ bool AddSpecialBoxController::checkInfoLoaded(
const auto channel = _peer->asChannel();
_api.request(MTPchannels_GetParticipant(
channel->inputChannel,
user->inputUser
participant->input
)).done([=](const MTPchannels_ChannelParticipant &result) {
Expects(result.type() == mtpc_channels_channelParticipant);
const auto &participant = result.c_channels_channelParticipant();
channel->owner().processUsers(participant.vusers());
_additional.applyParticipant(participant.vparticipant());
result.match([&](const MTPDchannels_channelParticipant &data) {
channel->owner().processUsers(data.vusers());
_additional.applyParticipant(data.vparticipant());
});
callback();
}).fail([=](const MTP::Error &error) {
_additional.setExternal(user);
_additional.setExternal(participant);
callback();
}).send();
return false;
@@ -724,15 +730,13 @@ void AddSpecialBoxController::showRestricted(
// Finally edit the restricted.
const auto currentRights = restrictedRights
? *restrictedRights
: MTPChatBannedRights(MTP_chatBannedRights(
MTP_flags(0),
MTP_int(0)));
: ChannelData::EmptyRestrictedRights(user);
auto box = Box<EditRestrictedBox>(
_peer,
user,
_additional.adminRights(user).has_value(),
currentRights);
if (_additional.canRestrictUser(user)) {
if (_additional.canRestrictParticipant(user)) {
const auto done = crl::guard(this, [=](
const MTPChatBannedRights &newRights) {
editRestrictedDone(user, newRights);
@@ -749,50 +753,61 @@ void AddSpecialBoxController::showRestricted(
}
void AddSpecialBoxController::editRestrictedDone(
not_null<UserData*> user,
not_null<PeerData*> participant,
const MTPChatBannedRights &rights) {
if (_editParticipantBox) {
_editParticipantBox->closeBox();
}
const auto date = base::unixtime::now(); // Incorrect, but ignored.
if (rights.c_chatBannedRights().vflags().v == 0) {
_additional.applyParticipant(MTP_channelParticipant(
MTP_int(user->bareId()),
MTP_int(date)));
if (Data::ChatBannedRightsFlags(rights) == 0) {
if (const auto user = participant->asUser()) {
_additional.applyParticipant(MTP_channelParticipant(
MTP_int(user->bareId()),
MTP_int(date)));
} else {
_additional.setExternal(participant);
}
} else {
const auto kicked = rights.c_chatBannedRights().is_view_messages();
const auto alreadyRestrictedBy = _additional.restrictedBy(user);
const auto kicked = Data::ChatBannedRightsFlags(rights)
& ChatRestriction::f_view_messages;
const auto alreadyRestrictedBy = _additional.restrictedBy(
participant);
_additional.applyParticipant(MTP_channelParticipantBanned(
MTP_flags(kicked
? MTPDchannelParticipantBanned::Flag::f_left
: MTPDchannelParticipantBanned::Flag(0)),
MTP_int(user->bareId()),
(participant->isUser()
? MTP_peerUser(MTP_int(participant->bareId()))
: participant->isChat()
? MTP_peerChat(MTP_int(participant->bareId()))
: MTP_peerChannel(MTP_int(participant->bareId()))),
MTP_int(alreadyRestrictedBy
? alreadyRestrictedBy->bareId()
: user->session().userId()),
: participant->session().userId()),
MTP_int(date),
rights));
}
if (const auto callback = _bannedDoneCallback) {
callback(user, rights);
callback(participant, rights);
}
}
void AddSpecialBoxController::kickUser(
not_null<UserData*> user,
not_null<PeerData*> participant,
bool sure) {
if (!checkInfoLoaded(user, [=] { kickUser(user); })) {
if (!checkInfoLoaded(participant, [=] { kickUser(participant); })) {
return;
}
const auto kickUserSure = crl::guard(this, [=] {
kickUser(user, true);
kickUser(participant, true);
});
// Check restrictions.
if (_additional.adminRights(user).has_value()
|| _additional.isCreator(user)) {
const auto user = participant->asUser();
if (user && (_additional.adminRights(user).has_value()
|| (_additional.isCreator(user)))) {
// The user is an admin or creator.
if (!_additional.isCreator(user) && _additional.canEditAdmin(user)) {
if (!sure) {
@@ -818,37 +833,39 @@ void AddSpecialBoxController::kickUser(
: tr::lng_profile_sure_kick_channel)(
tr::now,
lt_user,
user->name);
participant->name);
_editBox = Ui::show(
Box<ConfirmBox>(text, kickUserSure),
Ui::LayerOption::KeepOther);
return;
}
const auto restrictedRights = _additional.restrictedRights(user);
const auto restrictedRights = _additional.restrictedRights(participant);
const auto currentRights = restrictedRights
? *restrictedRights
: MTPChatBannedRights(MTP_chatBannedRights(
MTP_flags(0),
MTP_int(0)));
: ChannelData::EmptyRestrictedRights(participant);
const auto done = crl::guard(this, [=](
const MTPChatBannedRights &newRights) {
editRestrictedDone(user, newRights);
editRestrictedDone(participant, newRights);
});
const auto fail = crl::guard(this, [=] {
_editBox = nullptr;
});
const auto callback = SaveRestrictedCallback(_peer, user, done, fail);
callback(currentRights, ChannelData::KickedRestrictedRights());
const auto callback = SaveRestrictedCallback(
_peer,
participant,
done,
fail);
callback(currentRights, ChannelData::KickedRestrictedRights(participant));
}
bool AddSpecialBoxController::appendRow(not_null<UserData*> user) {
if (delegate()->peerListFindRow(user->id)
|| (_excludeSelf && user->isSelf())) {
bool AddSpecialBoxController::appendRow(not_null<PeerData*> participant) {
if (delegate()->peerListFindRow(participant->id)
|| (_excludeSelf && participant->isSelf())) {
return false;
}
delegate()->peerListAppendRow(createRow(user));
delegate()->peerListAppendRow(createRow(participant));
return true;
}
@@ -861,8 +878,8 @@ bool AddSpecialBoxController::prependRow(not_null<UserData*> user) {
}
std::unique_ptr<PeerListRow> AddSpecialBoxController::createRow(
not_null<UserData*> user) const {
return std::make_unique<PeerListRow>(user);
not_null<PeerData*> participant) const {
return std::make_unique<PeerListRow>(participant);
}
AddSpecialBoxSearchController::AddSpecialBoxSearchController(

View File

@@ -80,7 +80,7 @@ public:
const MTPChatAdminRights &adminRights,
const QString &rank)>;
using BannedDoneCallback = Fn<void(
not_null<UserData*> user,
not_null<PeerData*> participant,
const MTPChatBannedRights &bannedRights)>;
AddSpecialBoxController(
not_null<PeerData*> peer,
@@ -101,7 +101,7 @@ public:
private:
template <typename Callback>
bool checkInfoLoaded(not_null<UserData*> user, Callback callback);
bool checkInfoLoaded(not_null<PeerData*> participant, Callback callback);
void prepareChatRows(not_null<ChatData*> chat);
void rebuildChatRows(not_null<ChatData*> chat);
@@ -113,12 +113,13 @@ private:
const QString &rank);
void showRestricted(not_null<UserData*> user, bool sure = false);
void editRestrictedDone(
not_null<UserData*> user,
not_null<PeerData*> participant,
const MTPChatBannedRights &rights);
void kickUser(not_null<UserData*> user, bool sure = false);
bool appendRow(not_null<UserData*> user);
void kickUser(not_null<PeerData*> participant, bool sure = false);
bool appendRow(not_null<PeerData*> participant);
bool prependRow(not_null<UserData*> user);
std::unique_ptr<PeerListRow> createRow(not_null<UserData*> user) const;
std::unique_ptr<PeerListRow> createRow(
not_null<PeerData*> participant) const;
void subscribeToMigration();
void migrate(not_null<ChatData*> chat, not_null<ChannelData*> channel);

View File

@@ -222,7 +222,8 @@ MTPChatAdminRights EditAdminBox::defaultRights() const {
| Flag::f_post_messages
| Flag::f_edit_messages
| Flag::f_delete_messages
| Flag::f_invite_users);
| Flag::f_invite_users
| Flag::f_manage_call);
return MTP_chatAdminRights(MTP_flags(flags));
}
@@ -611,11 +612,11 @@ void EditRestrictedBox::prepare() {
const auto defaultRestrictions = chat
? chat->defaultRestrictions()
: channel->defaultRestrictions();
const auto prepareRights = _oldRights.c_chatBannedRights().vflags().v
const auto prepareRights = Data::ChatBannedRightsFlags(_oldRights)
? _oldRights
: defaultRights();
const auto prepareFlags = FixDependentRestrictions(
prepareRights.c_chatBannedRights().vflags().v
Data::ChatBannedRightsFlags(prepareRights)
| defaultRestrictions
| ((channel && channel->isPublic())
? (Flag::f_change_info | Flag::f_pin_messages)
@@ -646,7 +647,7 @@ void EditRestrictedBox::prepare() {
disabledMessages);
addControl(std::move(checkboxes), QMargins());
_until = prepareRights.c_chatBannedRights().vuntil_date().v;
_until = Data::ChatBannedRightsUntilDate(prepareRights);
addControl(object_ptr<Ui::BoxContentDivider>(this), st::rightsUntilMargin);
addControl(
object_ptr<Ui::FlatLabel>(
@@ -766,7 +767,7 @@ void EditRestrictedBox::createUntilVariants() {
}
};
auto addCurrentVariant = [&](TimeId from, TimeId to) {
auto oldUntil = _oldRights.c_chatBannedRights().vuntil_date().v;
auto oldUntil = Data::ChatBannedRightsUntilDate(_oldRights);
if (oldUntil < _until) {
addCustomVariant(oldUntil, from, to);
}

View File

@@ -146,18 +146,18 @@ void SaveChannelAdmin(
void SaveChannelRestriction(
not_null<ChannelData*> channel,
not_null<UserData*> user,
not_null<PeerData*> participant,
const MTPChatBannedRights &oldRights,
const MTPChatBannedRights &newRights,
Fn<void()> onDone,
Fn<void()> onFail) {
channel->session().api().request(MTPchannels_EditBanned(
channel->inputChannel,
user->inputUser,
participant->input,
newRights
)).done([=](const MTPUpdates &result) {
channel->session().api().applyUpdates(result);
channel->applyEditBanned(user, oldRights, newRights);
channel->applyEditBanned(participant, oldRights, newRights);
if (onDone) {
onDone();
}
@@ -243,7 +243,7 @@ Fn<void(
const MTPChatBannedRights &oldRights,
const MTPChatBannedRights &newRights)> SaveRestrictedCallback(
not_null<PeerData*> peer,
not_null<UserData*> user,
not_null<PeerData*> participant,
Fn<void(const MTPChatBannedRights &newRights)> onDone,
Fn<void()> onFail) {
return [=](
@@ -253,19 +253,21 @@ Fn<void(
const auto saveForChannel = [=](not_null<ChannelData*> channel) {
SaveChannelRestriction(
channel,
user,
participant,
oldRights,
newRights,
done,
onFail);
};
if (const auto chat = peer->asChatNotMigrated()) {
const auto flags = newRights.match([](
const MTPDchatBannedRights &data) {
return data.vflags().v;
});
if (flags & MTPDchatBannedRights::Flag::f_view_messages) {
SaveChatParticipantKick(chat, user, done, onFail);
const auto flags = Data::ChatBannedRightsFlags(newRights);
if (participant->isUser()
&& (flags & MTPDchatBannedRights::Flag::f_view_messages)) {
SaveChatParticipantKick(
chat,
participant->asUser(),
done,
onFail);
} else if (!flags) {
done();
} else {
@@ -313,9 +315,9 @@ ParticipantsAdditionalData::ParticipantsAdditionalData(
}
bool ParticipantsAdditionalData::infoLoaded(
not_null<UserData*> user) const {
not_null<PeerData*> participant) const {
return _peer->isChat()
|| (_infoNotLoaded.find(user) == end(_infoNotLoaded));
|| (_infoNotLoaded.find(participant) == end(_infoNotLoaded));
}
bool ParticipantsAdditionalData::canEditAdmin(
@@ -342,24 +344,27 @@ bool ParticipantsAdditionalData::canAddOrEditAdmin(
Unexpected("Peer in ParticipantsAdditionalData::canAddOrEditAdmin.");
}
bool ParticipantsAdditionalData::canRestrictUser(
not_null<UserData*> user) const {
if (!canEditAdmin(user) || user->isSelf()) {
bool ParticipantsAdditionalData::canRestrictParticipant(
not_null<PeerData*> participant) const {
const auto user = participant->asUser();
if (user && (!canEditAdmin(user) || user->isSelf())) {
return false;
} else if (const auto chat = _peer->asChat()) {
return chat->canBanMembers();
} else if (const auto channel = _peer->asChannel()) {
return channel->canBanMembers();
}
Unexpected("Peer in ParticipantsAdditionalData::canRestrictUser.");
Unexpected("Peer in ParticipantsAdditionalData::canRestrictParticipant.");
}
bool ParticipantsAdditionalData::canRemoveUser(
not_null<UserData*> user) const {
if (canRestrictUser(user)) {
bool ParticipantsAdditionalData::canRemoveParticipant(
not_null<PeerData*> participant) const {
const auto user = participant->asUser();
if (canRestrictParticipant(participant)) {
return true;
} else if (const auto chat = _peer->asChat()) {
return !user->isSelf()
return user
&& !user->isSelf()
&& chat->invitedByMe.contains(user)
&& (chat->amCreator() || !_admins.contains(user));
}
@@ -388,12 +393,12 @@ QString ParticipantsAdditionalData::adminRank(
}
auto ParticipantsAdditionalData::restrictedRights(
not_null<UserData*> user) const
not_null<PeerData*> participant) const
-> std::optional<MTPChatBannedRights> {
if (_peer->isChat()) {
return std::nullopt;
}
const auto i = _restrictedRights.find(user);
const auto i = _restrictedRights.find(participant);
return (i != end(_restrictedRights))
? std::make_optional(i->second)
: std::nullopt;
@@ -404,16 +409,18 @@ bool ParticipantsAdditionalData::isCreator(not_null<UserData*> user) const {
}
bool ParticipantsAdditionalData::isExternal(
not_null<UserData*> user) const {
not_null<PeerData*> participant) const {
return _peer->isChat()
? !_members.contains(user)
: _external.find(user) != end(_external);
? (participant->isUser()
&& !_members.contains(participant->asUser()))
: _external.find(participant) != end(_external);
}
bool ParticipantsAdditionalData::isKicked(not_null<UserData*> user) const {
bool ParticipantsAdditionalData::isKicked(
not_null<PeerData*> participant) const {
return _peer->isChat()
? false
: _kicked.find(user) != end(_kicked);
: _kicked.find(participant) != end(_kicked);
}
UserData *ParticipantsAdditionalData::adminPromotedBy(
@@ -426,29 +433,41 @@ UserData *ParticipantsAdditionalData::adminPromotedBy(
}
UserData *ParticipantsAdditionalData::restrictedBy(
not_null<UserData*> user) const {
not_null<PeerData*> participant) const {
if (_peer->isChat()) {
return nullptr;
}
const auto i = _restrictedBy.find(user);
const auto i = _restrictedBy.find(participant);
return (i != end(_restrictedBy)) ? i->second.get() : nullptr;
}
void ParticipantsAdditionalData::setExternal(not_null<UserData*> user) {
_infoNotLoaded.erase(user);
_external.emplace(user);
void ParticipantsAdditionalData::setExternal(
not_null<PeerData*> participant) {
if (const auto user = participant->asUser()) {
_adminRights.erase(user);
_adminCanEdit.erase(user);
_adminPromotedBy.erase(user);
_admins.erase(user);
}
_restrictedRights.erase(participant);
_kicked.erase(participant);
_restrictedBy.erase(participant);
_infoNotLoaded.erase(participant);
_external.emplace(participant);
}
void ParticipantsAdditionalData::checkForLoaded(not_null<UserData*> user) {
void ParticipantsAdditionalData::checkForLoaded(
not_null<PeerData*> participant) {
const auto contains = [](const auto &map, const auto &value) {
return map.find(value) != map.end();
};
if (_creator != user
&& !contains(_adminRights, user)
&& !contains(_restrictedRights, user)
&& !contains(_external, user)
&& !contains(_kicked, user)) {
_infoNotLoaded.emplace(user);
const auto user = participant->asUser();
if (!(user && _creator == user)
&& !(user && contains(_adminRights, user))
&& !contains(_restrictedRights, participant)
&& !contains(_external, participant)
&& !contains(_kicked, participant)) {
_infoNotLoaded.emplace(participant);
}
}
@@ -510,15 +529,15 @@ void ParticipantsAdditionalData::fillFromChannel(
}
}
UserData *ParticipantsAdditionalData::applyParticipant(
PeerData *ParticipantsAdditionalData::applyParticipant(
const MTPChannelParticipant &data) {
return applyParticipant(data, _role);
}
UserData *ParticipantsAdditionalData::applyParticipant(
PeerData *ParticipantsAdditionalData::applyParticipant(
const MTPChannelParticipant &data,
Role overrideRole) {
const auto logBad = [&]() -> UserData* {
const auto logBad = [&]() -> PeerData* {
LOG(("API Error: Bad participant type %1 got "
"while requesting for participants, role: %2"
).arg(data.type()
@@ -526,27 +545,28 @@ UserData *ParticipantsAdditionalData::applyParticipant(
return nullptr;
};
return data.match([&](const MTPDchannelParticipantCreator &data) {
return data.match([&](
const MTPDchannelParticipantCreator &data) -> PeerData* {
if (overrideRole != Role::Profile
&& overrideRole != Role::Members
&& overrideRole != Role::Admins) {
return logBad();
}
return applyCreator(data);
}, [&](const MTPDchannelParticipantAdmin &data) {
}, [&](const MTPDchannelParticipantAdmin &data) -> PeerData* {
if (overrideRole != Role::Profile
&& overrideRole != Role::Members
&& overrideRole != Role::Admins) {
return logBad();
}
return applyAdmin(data);
}, [&](const MTPDchannelParticipantSelf &data) {
}, [&](const MTPDchannelParticipantSelf &data) -> PeerData* {
if (overrideRole != Role::Profile
&& overrideRole != Role::Members) {
return logBad();
}
return applyRegular(data.vuser_id());
}, [&](const MTPDchannelParticipant &data) {
}, [&](const MTPDchannelParticipant &data) -> PeerData* {
if (overrideRole != Role::Profile
&& overrideRole != Role::Members) {
return logBad();
@@ -645,32 +665,35 @@ UserData *ParticipantsAdditionalData::applyRegular(MTPint userId) {
return user;
}
UserData *ParticipantsAdditionalData::applyBanned(
PeerData *ParticipantsAdditionalData::applyBanned(
const MTPDchannelParticipantBanned &data) {
const auto user = _peer->owner().userLoaded(data.vuser_id().v);
if (!user) {
const auto participant = _peer->owner().peerLoaded(
peerFromMTP(data.vpeer()));
if (!participant) {
return nullptr;
}
_infoNotLoaded.erase(user);
_adminRights.erase(user);
_adminCanEdit.erase(user);
_adminPromotedBy.erase(user);
if (data.is_left()) {
_kicked.emplace(user);
} else {
_kicked.erase(user);
_infoNotLoaded.erase(participant);
if (const auto user = participant->asUser()) {
_adminRights.erase(user);
_adminCanEdit.erase(user);
_adminPromotedBy.erase(user);
}
_restrictedRights[user] = data.vbanned_rights();
if (data.is_left()) {
_kicked.emplace(participant);
} else {
_kicked.erase(participant);
}
_restrictedRights[participant] = data.vbanned_rights();
if (const auto by = _peer->owner().userLoaded(data.vkicked_by().v)) {
const auto i = _restrictedBy.find(user);
const auto i = _restrictedBy.find(participant);
if (i == _restrictedBy.end()) {
_restrictedBy.emplace(user, by);
_restrictedBy.emplace(participant, by);
} else {
i->second = by;
}
}
return user;
return participant;
}
void ParticipantsAdditionalData::migrate(
@@ -922,9 +945,9 @@ void ParticipantsBoxController::addNewItem() {
editAdminDone(user, rights, rank);
});
const auto restrictedDone = crl::guard(this, [=](
not_null<UserData*> user,
not_null<PeerData*> participant,
const MTPChatBannedRights &rights) {
editRestrictedDone(user, rights);
editRestrictedDone(participant, rights);
});
const auto initBox = [](not_null<PeerListBox*> box) {
box->addButton(tr::lng_cancel(), [=] { box->closeBox(); });
@@ -955,8 +978,10 @@ void ParticipantsBoxController::addNewParticipants() {
auto already = std::vector<not_null<UserData*>>();
already.reserve(count);
for (auto i = 0; i != count; ++i) {
already.emplace_back(
delegate()->peerListRowAt(i)->peer()->asUser());
const auto participant = delegate()->peerListRowAt(i)->peer();
if (const auto user = participant->asUser()) {
already.emplace_back(user);
}
}
AddParticipantsBoxController::Start(
_navigation,
@@ -1165,6 +1190,7 @@ void ParticipantsBoxController::rebuildChatParticipants(
auto count = delegate()->peerListFullRowsCount();
for (auto i = 0; i != count;) {
auto row = delegate()->peerListRowAt(i);
Assert(row->peer()->isUser());
auto user = row->peer()->asUser();
if (participants.contains(user)) {
++i;
@@ -1316,8 +1342,9 @@ void ParticipantsBoxController::loadMoreRows() {
int availableCount,
const QVector<MTPChannelParticipant> &list) {
for (const auto &data : list) {
if (const auto user = _additional.applyParticipant(data)) {
appendRow(user);
if (const auto participant = _additional.applyParticipant(
data)) {
appendRow(participant);
}
}
if (const auto size = list.size()) {
@@ -1398,31 +1425,32 @@ bool ParticipantsBoxController::feedMegagroupLastParticipants() {
}
void ParticipantsBoxController::rowClicked(not_null<PeerListRow*> row) {
Expects(row->peer()->isUser());
const auto user = row->peer()->asUser();
const auto participant = row->peer();
const auto user = participant->asUser();
if (_role == Role::Admins) {
Assert(user != nullptr);
showAdmin(user);
} else if (_role == Role::Restricted
&& (_peer->isChat() || _peer->isMegagroup())) {
&& (_peer->isChat() || _peer->isMegagroup())
&& user) {
showRestricted(user);
} else {
Assert(_navigation != nullptr);
_navigation->showPeerInfo(user);
_navigation->showPeerInfo(participant);
}
}
void ParticipantsBoxController::rowActionClicked(
not_null<PeerListRow*> row) {
Expects(row->peer()->isUser());
const auto user = row->peer()->asUser();
const auto participant = row->peer();
const auto user = participant->asUser();
if (_role == Role::Members || _role == Role::Profile) {
kickMember(user);
kickParticipant(participant);
} else if (_role == Role::Admins) {
Assert(user != nullptr);
removeAdmin(user);
} else {
removeKicked(row, user);
removeKicked(row, participant);
}
}
@@ -1433,28 +1461,30 @@ base::unique_qptr<Ui::PopupMenu> ParticipantsBoxController::rowContextMenu(
const auto chat = _peer->asChat();
const auto channel = _peer->asChannel();
const auto user = row->peer()->asUser();
const auto participant = row->peer();
const auto user = participant->asUser();
auto result = base::make_unique_q<Ui::PopupMenu>(parent);
if (_navigation) {
result->addAction(
tr::lng_context_view_profile(tr::now),
crl::guard(this, [=] { _navigation->showPeerInfo(user); }));
crl::guard(this, [=] {
_navigation->showPeerInfo(participant); }));
}
if (_role == Role::Kicked) {
if (_peer->isMegagroup()
&& _additional.canRestrictUser(user)) {
if (channel->canAddMembers()) {
&& _additional.canRestrictParticipant(participant)) {
if (user && channel->canAddMembers()) {
result->addAction(
tr::lng_context_add_to_group(tr::now),
crl::guard(this, [=] { unkickMember(user); }));
crl::guard(this, [=] { unkickParticipant(user); }));
}
result->addAction(
tr::lng_profile_delete_removed(tr::now),
crl::guard(this, [=] { removeKickedWithRow(user); }));
crl::guard(this, [=] { removeKickedWithRow(participant); }));
}
return result;
}
if (_additional.canAddOrEditAdmin(user)) {
if (user && _additional.canAddOrEditAdmin(user)) {
const auto isAdmin = _additional.isCreator(user)
|| _additional.adminRights(user).has_value();
result->addAction(
@@ -1463,7 +1493,7 @@ base::unique_qptr<Ui::PopupMenu> ParticipantsBoxController::rowContextMenu(
: tr::lng_context_promote_admin)(tr::now),
crl::guard(this, [=] { showAdmin(user); }));
}
if (_additional.canRestrictUser(user)) {
if (_additional.canRestrictParticipant(participant)) {
const auto canRestrictWithoutKick = [&] {
if (const auto chat = _peer->asChat()) {
return chat->amCreator();
@@ -1476,14 +1506,14 @@ base::unique_qptr<Ui::PopupMenu> ParticipantsBoxController::rowContextMenu(
crl::guard(this, [=] { showRestricted(user); }));
}
}
if (_additional.canRemoveUser(user)) {
if (!_additional.isKicked(user)) {
if (_additional.canRemoveParticipant(participant)) {
if (!_additional.isKicked(participant)) {
const auto isGroup = _peer->isChat() || _peer->isMegagroup();
result->addAction(
(isGroup
? tr::lng_context_remove_from_group
: tr::lng_profile_kick)(tr::now),
crl::guard(this, [=] { kickMember(user); }));
crl::guard(this, [=] { kickParticipant(user); }));
}
}
return result;
@@ -1569,9 +1599,7 @@ void ParticipantsBoxController::showRestricted(not_null<UserData*> user) {
const auto restrictedRights = _additional.restrictedRights(user);
const auto currentRights = restrictedRights
? *restrictedRights
: MTPChatBannedRights(MTP_chatBannedRights(
MTP_flags(0),
MTP_int(0)));
: ChannelData::EmptyRestrictedRights(user);
const auto hasAdminRights = _additional.adminRights(user).has_value();
auto box = Box<EditRestrictedBox>(
_peer,
@@ -1580,7 +1608,7 @@ void ParticipantsBoxController::showRestricted(not_null<UserData*> user) {
currentRights);
const auto chat = _peer->asChat();
const auto channel = _peer->asChannel();
if (_additional.canRestrictUser(user)) {
if (_additional.canRestrictParticipant(user)) {
const auto done = crl::guard(this, [=](
const MTPChatBannedRights &newRights) {
editRestrictedDone(user, newRights);
@@ -1597,72 +1625,84 @@ void ParticipantsBoxController::showRestricted(not_null<UserData*> user) {
}
void ParticipantsBoxController::editRestrictedDone(
not_null<UserData*> user,
not_null<PeerData*> participant,
const MTPChatBannedRights &rights) {
_addBox = nullptr;
if (_editParticipantBox) {
_editParticipantBox->closeBox();
}
const auto user = participant->asUser();
const auto date = base::unixtime::now(); // Incorrect, but ignored.
if (rights.c_chatBannedRights().vflags().v == 0) {
_additional.applyParticipant(MTP_channelParticipant(
MTP_int(user->bareId()),
MTP_int(date)));
if (Data::ChatBannedRightsFlags(rights) == 0) {
if (user) {
_additional.applyParticipant(MTP_channelParticipant(
MTP_int(user->bareId()),
MTP_int(date)));
} else {
_additional.setExternal(participant);
}
if (_role == Role::Kicked || _role == Role::Restricted) {
removeRow(user);
removeRow(participant);
}
} else {
const auto kicked = rights.c_chatBannedRights().is_view_messages();
const auto alreadyRestrictedBy = _additional.restrictedBy(user);
const auto kicked = Data::ChatBannedRightsFlags(rights)
& ChatRestriction::f_view_messages;
const auto alreadyRestrictedBy = _additional.restrictedBy(
participant);
_additional.applyParticipant(MTP_channelParticipantBanned(
MTP_flags(kicked
? MTPDchannelParticipantBanned::Flag::f_left
: MTPDchannelParticipantBanned::Flag(0)),
MTP_int(user->bareId()),
(participant->isUser()
? MTP_peerUser(MTP_int(participant->bareId()))
: participant->isChat()
? MTP_peerChat(MTP_int(participant->bareId()))
: MTP_peerChannel(MTP_int(participant->bareId()))),
MTP_int(alreadyRestrictedBy
? alreadyRestrictedBy->bareId()
: user->session().userId()),
: participant->session().userId()),
MTP_int(date),
rights));
if (kicked) {
if (_role == Role::Kicked) {
prependRow(user);
prependRow(participant);
} else if (_role == Role::Admins
|| _role == Role::Restricted
|| _role == Role::Members) {
removeRow(user);
removeRow(participant);
}
} else {
if (_role == Role::Restricted) {
prependRow(user);
prependRow(participant);
} else if (_role == Role::Kicked
|| _role == Role::Admins
|| _role == Role::Members) {
removeRow(user);
removeRow(participant);
}
}
}
recomputeTypeFor(user);
recomputeTypeFor(participant);
delegate()->peerListRefreshRows();
}
void ParticipantsBoxController::kickMember(not_null<UserData*> user) {
void ParticipantsBoxController::kickParticipant(not_null<PeerData*> participant) {
const auto user = participant->asUser();
const auto text = ((_peer->isChat() || _peer->isMegagroup())
? tr::lng_profile_sure_kick
: tr::lng_profile_sure_kick_channel)(
tr::now,
lt_user,
user->firstName);
user ? user->firstName : participant->name);
_editBox = Ui::show(
Box<ConfirmBox>(
text,
tr::lng_box_remove(tr::now),
crl::guard(this, [=] { kickMemberSure(user); })),
crl::guard(this, [=] { kickParticipantSure(participant); })),
Ui::LayerOption::KeepOther);
}
void ParticipantsBoxController::unkickMember(not_null<UserData*> user) {
void ParticipantsBoxController::unkickParticipant(not_null<UserData*> user) {
_editBox = nullptr;
if (const auto row = delegate()->peerListFindRow(user->id)) {
delegate()->peerListRemoveRow(row);
@@ -1671,25 +1711,24 @@ void ParticipantsBoxController::unkickMember(not_null<UserData*> user) {
_peer->session().api().addChatParticipants(_peer, { 1, user });
}
void ParticipantsBoxController::kickMemberSure(not_null<UserData*> user) {
void ParticipantsBoxController::kickParticipantSure(
not_null<PeerData*> participant) {
_editBox = nullptr;
const auto restrictedRights = _additional.restrictedRights(user);
const auto restrictedRights = _additional.restrictedRights(participant);
const auto currentRights = restrictedRights
? *restrictedRights
: MTPChatBannedRights(MTP_chatBannedRights(
MTP_flags(0),
MTP_int(0)));
: ChannelData::EmptyRestrictedRights(participant);
if (const auto row = delegate()->peerListFindRow(user->id)) {
if (const auto row = delegate()->peerListFindRow(participant->id)) {
delegate()->peerListRemoveRow(row);
delegate()->peerListRefreshRows();
}
auto &session = _peer->session();
if (const auto chat = _peer->asChat()) {
session.api().kickParticipant(chat, user);
session.api().kickParticipant(chat, participant);
} else if (const auto channel = _peer->asChannel()) {
session.api().kickParticipant(channel, user, currentRights);
session.api().kickParticipant(channel, participant, currentRights);
}
}
@@ -1730,36 +1769,37 @@ void ParticipantsBoxController::removeAdminSure(not_null<UserData*> user) {
}
void ParticipantsBoxController::removeKickedWithRow(
not_null<UserData*> user) {
if (const auto row = delegate()->peerListFindRow(user->id)) {
removeKicked(row, user);
not_null<PeerData*> participant) {
if (const auto row = delegate()->peerListFindRow(participant->id)) {
removeKicked(row, participant);
} else {
removeKicked(user);
removeKicked(participant);
}
}
void ParticipantsBoxController::removeKicked(not_null<UserData*> user) {
void ParticipantsBoxController::removeKicked(
not_null<PeerData*> participant) {
if (const auto channel = _peer->asChannel()) {
channel->session().api().unblockParticipant(channel, user);
channel->session().api().unblockParticipant(channel, participant);
}
}
void ParticipantsBoxController::removeKicked(
not_null<PeerListRow*> row,
not_null<UserData*> user) {
not_null<PeerData*> participant) {
delegate()->peerListRemoveRow(row);
if (_role != Role::Kicked
&& !delegate()->peerListFullRowsCount()) {
setDescriptionText(tr::lng_blocked_list_not_found(tr::now));
}
delegate()->peerListRefreshRows();
removeKicked(user);
removeKicked(participant);
}
bool ParticipantsBoxController::appendRow(not_null<UserData*> user) {
if (delegate()->peerListFindRow(user->id)) {
recomputeTypeFor(user);
bool ParticipantsBoxController::appendRow(not_null<PeerData*> participant) {
if (delegate()->peerListFindRow(participant->id)) {
recomputeTypeFor(participant);
return false;
} else if (auto row = createRow(user)) {
} else if (auto row = createRow(participant)) {
delegate()->peerListAppendRow(std::move(row));
if (_role != Role::Kicked) {
setDescriptionText(QString());
@@ -1769,16 +1809,16 @@ bool ParticipantsBoxController::appendRow(not_null<UserData*> user) {
return false;
}
bool ParticipantsBoxController::prependRow(not_null<UserData*> user) {
if (const auto row = delegate()->peerListFindRow(user->id)) {
recomputeTypeFor(user);
bool ParticipantsBoxController::prependRow(not_null<PeerData*> participant) {
if (const auto row = delegate()->peerListFindRow(participant->id)) {
recomputeTypeFor(participant);
refreshCustomStatus(row);
if (_role == Role::Admins) {
// Perhaps we've added a new admin from search.
delegate()->peerListPrependRowFromSearchResult(row);
}
return false;
} else if (auto row = createRow(user)) {
} else if (auto row = createRow(participant)) {
delegate()->peerListPrependRow(std::move(row));
if (_role != Role::Kicked) {
setDescriptionText(QString());
@@ -1788,8 +1828,8 @@ bool ParticipantsBoxController::prependRow(not_null<UserData*> user) {
return false;
}
bool ParticipantsBoxController::removeRow(not_null<UserData*> user) {
if (auto row = delegate()->peerListFindRow(user->id)) {
bool ParticipantsBoxController::removeRow(not_null<PeerData*> participant) {
if (auto row = delegate()->peerListFindRow(participant->id)) {
if (_role == Role::Admins) {
// Perhaps we are removing an admin from search results.
row->setCustomStatus(tr::lng_channel_admin_status_not_admin(tr::now));
@@ -1807,24 +1847,28 @@ bool ParticipantsBoxController::removeRow(not_null<UserData*> user) {
}
std::unique_ptr<PeerListRow> ParticipantsBoxController::createRow(
not_null<UserData*> user) const {
not_null<PeerData*> participant) const {
const auto user = participant->asUser();
if (_role == Role::Profile) {
Assert(user != nullptr);
return std::make_unique<Row>(user, computeType(user));
}
const auto chat = _peer->asChat();
const auto channel = _peer->asChannel();
auto row = std::make_unique<PeerListRowWithLink>(user);
auto row = std::make_unique<PeerListRowWithLink>(participant);
refreshCustomStatus(row.get());
if (_role == Role::Admins
&& user
&& !_additional.isCreator(user)
&& _additional.adminRights(user).has_value()
&& _additional.canEditAdmin(user)) {
row->setActionLink(tr::lng_profile_kick(tr::now));
} else if (_role == Role::Kicked || _role == Role::Restricted) {
if (_additional.canRestrictUser(user)) {
if (_additional.canRestrictParticipant(participant)) {
row->setActionLink(tr::lng_profile_delete_removed(tr::now));
}
} else if (_role == Role::Members) {
Assert(user != nullptr);
if ((chat ? chat->canBanMembers() : channel->canBanMembers())
&& !_additional.isCreator(user)
&& (!_additional.adminRights(user)
@@ -1842,31 +1886,34 @@ std::unique_ptr<PeerListRow> ParticipantsBoxController::createRow(
}
auto ParticipantsBoxController::computeType(
not_null<UserData*> user) const -> Type {
not_null<PeerData*> participant) const -> Type {
const auto user = participant->asUser();
auto result = Type();
result.rights = _additional.isCreator(user)
result.rights = (user && _additional.isCreator(user))
? Rights::Creator
: _additional.adminRights(user).has_value()
: (user && _additional.adminRights(user).has_value())
? Rights::Admin
: Rights::Normal;
result.canRemove = _additional.canRemoveUser(user);
result.canRemove = _additional.canRemoveParticipant(participant);
return result;
}
void ParticipantsBoxController::recomputeTypeFor(
not_null<UserData*> user) {
not_null<PeerData*> participant) {
if (_role != Role::Profile) {
return;
}
if (const auto row = delegate()->peerListFindRow(user->id)) {
static_cast<Row*>(row)->setType(computeType(user));
if (const auto row = delegate()->peerListFindRow(participant->id)) {
static_cast<Row*>(row)->setType(computeType(participant));
}
}
void ParticipantsBoxController::refreshCustomStatus(
not_null<PeerListRow*> row) const {
const auto user = row->peer()->asUser();
const auto participant = row->peer();
const auto user = participant->asUser();
if (_role == Role::Admins) {
Assert(user != nullptr);
if (const auto by = _additional.adminPromotedBy(user)) {
row->setCustomStatus(tr::lng_channel_admin_status_promoted_by(
tr::now,
@@ -1882,7 +1929,7 @@ void ParticipantsBoxController::refreshCustomStatus(
}
}
} else if (_role == Role::Kicked || _role == Role::Restricted) {
const auto by = _additional.restrictedBy(user);
const auto by = _additional.restrictedBy(participant);
row->setCustomStatus((_role == Role::Kicked
? tr::lng_channel_banned_status_removed_by
: tr::lng_channel_banned_status_restricted_by)(

View File

@@ -33,7 +33,7 @@ Fn<void(
const MTPChatBannedRights &oldRights,
const MTPChatBannedRights &newRights)> SaveRestrictedCallback(
not_null<PeerData*> peer,
not_null<UserData*> user,
not_null<PeerData*> participant,
Fn<void(const MTPChatBannedRights &newRights)> onDone,
Fn<void()> onFail);
@@ -77,29 +77,31 @@ public:
ParticipantsAdditionalData(not_null<PeerData*> peer, Role role);
UserData *applyParticipant(const MTPChannelParticipant &data);
UserData *applyParticipant(
PeerData *applyParticipant(const MTPChannelParticipant &data);
PeerData *applyParticipant(
const MTPChannelParticipant &data,
Role overrideRole);
void setExternal(not_null<UserData*> user);
void checkForLoaded(not_null<UserData*> user);
void setExternal(not_null<PeerData*> participant);
void checkForLoaded(not_null<PeerData*> participant);
void fillFromPeer();
[[nodiscard]] bool infoLoaded(not_null<UserData*> user) const;
[[nodiscard]] bool infoLoaded(not_null<PeerData*> participant) const;
[[nodiscard]] bool canEditAdmin(not_null<UserData*> user) const;
[[nodiscard]] bool canAddOrEditAdmin(not_null<UserData*> user) const;
[[nodiscard]] bool canRestrictUser(not_null<UserData*> user) const;
[[nodiscard]] bool canRemoveUser(not_null<UserData*> user) const;
[[nodiscard]] bool canRestrictParticipant(
not_null<PeerData*> participant) const;
[[nodiscard]] bool canRemoveParticipant(
not_null<PeerData*> participant) const;
[[nodiscard]] std::optional<MTPChatAdminRights> adminRights(
not_null<UserData*> user) const;
QString adminRank(not_null<UserData*> user) const;
[[nodiscard]] std::optional<MTPChatBannedRights> restrictedRights(
not_null<UserData*> user) const;
not_null<PeerData*> participant) const;
[[nodiscard]] bool isCreator(not_null<UserData*> user) const;
[[nodiscard]] bool isExternal(not_null<UserData*> user) const;
[[nodiscard]] bool isKicked(not_null<UserData*> user) const;
[[nodiscard]] bool isExternal(not_null<PeerData*> participant) const;
[[nodiscard]] bool isKicked(not_null<PeerData*> participant) const;
[[nodiscard]] UserData *adminPromotedBy(not_null<UserData*> user) const;
[[nodiscard]] UserData *restrictedBy(not_null<UserData*> user) const;
[[nodiscard]] UserData *restrictedBy(not_null<PeerData*> participant) const;
void migrate(not_null<ChatData*> chat, not_null<ChannelData*> channel);
@@ -107,7 +109,7 @@ private:
UserData *applyCreator(const MTPDchannelParticipantCreator &data);
UserData *applyAdmin(const MTPDchannelParticipantAdmin &data);
UserData *applyRegular(MTPint userId);
UserData *applyBanned(const MTPDchannelParticipantBanned &data);
PeerData *applyBanned(const MTPDchannelParticipantBanned &data);
void fillFromChat(not_null<ChatData*> chat);
void fillFromChannel(not_null<ChannelData*> channel);
@@ -124,11 +126,11 @@ private:
base::flat_map<not_null<UserData*>, QString> _adminRanks;
base::flat_set<not_null<UserData*>> _adminCanEdit;
base::flat_map<not_null<UserData*>, not_null<UserData*>> _adminPromotedBy;
std::map<not_null<UserData*>, MTPChatBannedRights> _restrictedRights;
std::set<not_null<UserData*>> _kicked;
std::map<not_null<UserData*>, not_null<UserData*>> _restrictedBy;
std::set<not_null<UserData*>> _external;
std::set<not_null<UserData*>> _infoNotLoaded;
std::map<not_null<PeerData*>, MTPChatBannedRights> _restrictedRights;
std::set<not_null<PeerData*>> _kicked;
std::map<not_null<PeerData*>, not_null<UserData*>> _restrictedBy;
std::set<not_null<PeerData*>> _external;
std::set<not_null<PeerData*>> _infoNotLoaded;
};
@@ -181,7 +183,7 @@ protected:
Role role);
virtual std::unique_ptr<PeerListRow> createRow(
not_null<UserData*> user) const;
not_null<PeerData*> participant) const;
private:
using Row = Info::Profile::MemberListRow;
@@ -223,23 +225,25 @@ private:
const QString &rank);
void showRestricted(not_null<UserData*> user);
void editRestrictedDone(
not_null<UserData*> user,
not_null<PeerData*> participant,
const MTPChatBannedRights &rights);
void removeKicked(not_null<PeerListRow*> row, not_null<UserData*> user);
void removeKickedWithRow(not_null<UserData*> user);
void removeKicked(not_null<UserData*> user);
void kickMember(not_null<UserData*> user);
void kickMemberSure(not_null<UserData*> user);
void unkickMember(not_null<UserData*> user);
void removeKicked(
not_null<PeerListRow*> row,
not_null<PeerData*> participant);
void removeKickedWithRow(not_null<PeerData*> participant);
void removeKicked(not_null<PeerData*> participant);
void kickParticipant(not_null<PeerData*> participant);
void kickParticipantSure(not_null<PeerData*> participant);
void unkickParticipant(not_null<UserData*> user);
void removeAdmin(not_null<UserData*> user);
void removeAdminSure(not_null<UserData*> user);
bool appendRow(not_null<UserData*> user);
bool prependRow(not_null<UserData*> user);
bool removeRow(not_null<UserData*> user);
bool appendRow(not_null<PeerData*> participant);
bool prependRow(not_null<PeerData*> participant);
bool removeRow(not_null<PeerData*> participant);
void refreshCustomStatus(not_null<PeerListRow*> row) const;
bool feedMegagroupLastParticipants();
Type computeType(not_null<UserData*> user) const;
void recomputeTypeFor(not_null<UserData*> user);
Type computeType(not_null<PeerData*> participant) const;
void recomputeTypeFor(not_null<PeerData*> participant);
void subscribeToMigration();
void migrate(not_null<ChatData*> chat, not_null<ChannelData*> channel);

View File

@@ -151,6 +151,7 @@ std::vector<std::pair<ChatAdminRights, QString>> AdminRightLabels(
{ Flag::f_edit_messages, tr::lng_rights_channel_edit(tr::now) },
{ Flag::f_delete_messages, tr::lng_rights_channel_delete(tr::now) },
{ Flag::f_invite_users, tr::lng_rights_group_invite(tr::now) },
{ Flag::f_manage_call, tr::lng_rights_group_manage_calls(tr::now) },
{ Flag::f_add_admins, tr::lng_rights_add_admins(tr::now) }
};
}

View File

@@ -163,6 +163,37 @@ void ChooseJoinAsBox(
box->addButton(tr::lng_cancel(), [=] { box->closeBox(); });
}
[[nodiscard]] TextWithEntities CreateOrJoinConfirmation(
not_null<PeerData*> peer,
ChooseJoinAsProcess::Context context,
bool joinAsAlreadyUsed) {
const auto existing = peer->groupCall();
if (!existing) {
return { peer->isBroadcast()
? tr::lng_group_call_create_sure_channel(tr::now)
: tr::lng_group_call_create_sure(tr::now) };
}
const auto channel = peer->asChannel();
const auto anonymouseAdmin = channel
&& ((channel->isMegagroup() && channel->amAnonymous())
|| (channel->isBroadcast()
&& (channel->amCreator()
|| channel->hasAdminRights())));
if (anonymouseAdmin && !joinAsAlreadyUsed) {
return { tr::lng_group_call_join_sure_personal(tr::now) };
} else if (context != ChooseJoinAsProcess::Context::JoinWithConfirm) {
return {};
}
const auto name = !existing->title().isEmpty()
? existing->title()
: peer->name;
return tr::lng_group_call_join_confirm(
tr::now,
lt_chat,
Ui::Text::Bold(name),
Ui::Text::WithEntities);
}
} // namespace
ChooseJoinAsProcess::~ChooseJoinAsProcess() {
@@ -257,31 +288,27 @@ void ChooseJoinAsProcess::start(
info.possibleJoinAs = std::move(list);
const auto onlyByMe = (info.possibleJoinAs.size() == 1)
&& (info.possibleJoinAs.front() == self)
&& (!peer->isChannel()
|| !peer->asChannel()->amAnonymous()
|| (peer->isBroadcast() && !peer->canWrite()));
&& (info.possibleJoinAs.front() == self);
// We already joined this voice chat, just rejoin with the same.
const auto byAlreadyUsed = selectedId
&& (info.joinAs->id == selectedId);
&& (info.joinAs->id == selectedId)
&& (peer->groupCall() != nullptr);
if (!changingJoinAsFrom && (onlyByMe || byAlreadyUsed)) {
if (context != Context::JoinWithConfirm) {
const auto confirmation = CreateOrJoinConfirmation(
peer,
context,
byAlreadyUsed);
if (confirmation.text.isEmpty()) {
finish(info);
return;
}
const auto real = peer->groupCall();
const auto name = (real && !real->title().isEmpty())
? real->title()
: peer->name;
auto box = Box<::ConfirmBox>(
tr::lng_group_call_join_confirm(
tr::now,
lt_chat,
Ui::Text::Bold(name),
Ui::Text::WithEntities),
tr::lng_group_call_join(tr::now),
confirmation,
(peer->groupCall()
? tr::lng_group_call_join(tr::now)
: tr::lng_create_group_create(tr::now)),
crl::guard(&_request->guard, [=] { finish(info); }));
box->boxClosing(
) | rpl::start_with_next([=] {

View File

@@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "calls/calls_group_common.h"
#include "main/main_session.h"
#include "api/api_send_progress.h"
#include "api/api_updates.h"
#include "apiwrap.h"
#include "lang/lang_keys.h"
#include "lang/lang_hardcoded.h"
@@ -387,8 +388,11 @@ void GroupCall::join(const MTPInputGroupCall &inputCall) {
addParticipantsToInstance();
_peer->session().updates().addActiveChat(
_peerStream.events_starting_with_copy(_peer));
SubscribeToMigration(_peer, _lifetime, [=](not_null<ChannelData*> group) {
_peer = group;
_peerStream.fire_copy(group);
});
}
@@ -467,6 +471,7 @@ void GroupCall::rejoin(not_null<PeerData*> as) {
MTP_dataJSON(MTP_bytes(json))
)).done([=](const MTPUpdates &updates) {
_mySsrc = ssrc;
_mySsrcs.emplace(ssrc);
setState((_instanceState.current()
== InstanceState::Disconnected)
? State::Connecting
@@ -713,101 +718,112 @@ void GroupCall::setMutedAndUpdate(MuteState mute) {
}
}
void GroupCall::handleUpdate(const MTPGroupCall &call) {
return call.match([&](const MTPDgroupCall &data) {
if (_acceptFields) {
if (!_instance && !_id) {
join(MTP_inputGroupCall(data.vid(), data.vaccess_hash()));
}
void GroupCall::handlePossibleCreateOrJoinResponse(
const MTPDupdateGroupCall &data) {
data.vcall().match([&](const MTPDgroupCall &data) {
handlePossibleCreateOrJoinResponse(data);
}, [&](const MTPDgroupCallDiscarded &data) {
handlePossibleDiscarded(data);
});
}
void GroupCall::handlePossibleCreateOrJoinResponse(
const MTPDgroupCall &data) {
if (_acceptFields) {
if (!_instance && !_id) {
join(MTP_inputGroupCall(data.vid(), data.vaccess_hash()));
}
return;
} else if (_id != data.vid().v || !_instance) {
return;
}
const auto streamDcId = MTP::BareDcId(
data.vstream_dc_id().value_or_empty());
const auto params = data.vparams();
if (!params) {
return;
}
params->match([&](const MTPDdataJSON &data) {
auto error = QJsonParseError{ 0, QJsonParseError::NoError };
const auto document = QJsonDocument::fromJson(
data.vdata().v,
&error);
if (error.error != QJsonParseError::NoError) {
LOG(("API Error: "
"Failed to parse group call params, error: %1."
).arg(error.errorString()));
return;
} else if (_id != data.vid().v
|| _accessHash != data.vaccess_hash().v
|| !_instance) {
} else if (!document.isObject()) {
LOG(("API Error: "
"Not an object received in group call params."));
return;
}
const auto streamDcId = MTP::BareDcId(
data.vstream_dc_id().value_or_empty());
if (const auto params = data.vparams()) {
params->match([&](const MTPDdataJSON &data) {
auto error = QJsonParseError{ 0, QJsonParseError::NoError };
const auto document = QJsonDocument::fromJson(
data.vdata().v,
&error);
if (error.error != QJsonParseError::NoError) {
LOG(("API Error: "
"Failed to parse group call params, error: %1."
).arg(error.errorString()));
return;
} else if (!document.isObject()) {
LOG(("API Error: "
"Not an object received in group call params."));
return;
}
const auto guard = gsl::finally([&] {
addParticipantsToInstance();
});
const auto guard = gsl::finally([&] {
addParticipantsToInstance();
});
if (document.object().value("stream").toBool()) {
if (!streamDcId) {
LOG(("Api Error: Empty stream_dc_id in groupCall."));
}
_broadcastDcId = streamDcId
? streamDcId
: _peer->session().mtp().mainDcId();
setInstanceMode(InstanceMode::Stream);
return;
}
if (document.object().value("stream").toBool()) {
if (!streamDcId) {
LOG(("Api Error: Empty stream_dc_id in groupCall."));
}
_broadcastDcId = streamDcId
? streamDcId
: _peer->session().mtp().mainDcId();
setInstanceMode(InstanceMode::Stream);
return;
}
const auto readString = [](
const QJsonObject &object,
const char *key) {
return object.value(key).toString().toStdString();
};
const auto root = document.object().value("transport").toObject();
auto payload = tgcalls::GroupJoinResponsePayload();
payload.ufrag = readString(root, "ufrag");
payload.pwd = readString(root, "pwd");
const auto prints = root.value("fingerprints").toArray();
const auto candidates = root.value("candidates").toArray();
for (const auto &print : prints) {
const auto object = print.toObject();
payload.fingerprints.push_back(tgcalls::GroupJoinPayloadFingerprint{
.hash = readString(object, "hash"),
.setup = readString(object, "setup"),
.fingerprint = readString(object, "fingerprint"),
});
}
for (const auto &candidate : candidates) {
const auto object = candidate.toObject();
payload.candidates.push_back(tgcalls::GroupJoinResponseCandidate{
.port = readString(object, "port"),
.protocol = readString(object, "protocol"),
.network = readString(object, "network"),
.generation = readString(object, "generation"),
.id = readString(object, "id"),
.component = readString(object, "component"),
.foundation = readString(object, "foundation"),
.priority = readString(object, "priority"),
.ip = readString(object, "ip"),
.type = readString(object, "type"),
.tcpType = readString(object, "tcpType"),
.relAddr = readString(object, "relAddr"),
.relPort = readString(object, "relPort"),
});
}
setInstanceMode(InstanceMode::Rtc);
_instance->setJoinResponsePayload(payload, {});
const auto readString = [](
const QJsonObject &object,
const char *key) {
return object.value(key).toString().toStdString();
};
const auto root = document.object().value("transport").toObject();
auto payload = tgcalls::GroupJoinResponsePayload();
payload.ufrag = readString(root, "ufrag");
payload.pwd = readString(root, "pwd");
const auto prints = root.value("fingerprints").toArray();
const auto candidates = root.value("candidates").toArray();
for (const auto &print : prints) {
const auto object = print.toObject();
payload.fingerprints.push_back(tgcalls::GroupJoinPayloadFingerprint{
.hash = readString(object, "hash"),
.setup = readString(object, "setup"),
.fingerprint = readString(object, "fingerprint"),
});
}
}, [&](const MTPDgroupCallDiscarded &data) {
if (data.vid().v == _id) {
_mySsrc = 0;
hangup();
for (const auto &candidate : candidates) {
const auto object = candidate.toObject();
payload.candidates.push_back(tgcalls::GroupJoinResponseCandidate{
.port = readString(object, "port"),
.protocol = readString(object, "protocol"),
.network = readString(object, "network"),
.generation = readString(object, "generation"),
.id = readString(object, "id"),
.component = readString(object, "component"),
.foundation = readString(object, "foundation"),
.priority = readString(object, "priority"),
.ip = readString(object, "ip"),
.type = readString(object, "type"),
.tcpType = readString(object, "tcpType"),
.relAddr = readString(object, "relAddr"),
.relPort = readString(object, "relPort"),
});
}
setInstanceMode(InstanceMode::Rtc);
_instance->setJoinResponsePayload(payload, {});
});
}
void GroupCall::handlePossibleDiscarded(const MTPDgroupCallDiscarded &data) {
if (data.vid().v == _id) {
LOG(("Call Info: Hangup after groupCallDiscarded."));
_mySsrc = 0;
hangup();
}
}
void GroupCall::addParticipantsToInstance() {
const auto real = _peer->groupCall();
if (!real
@@ -849,7 +865,30 @@ void GroupCall::addPreparedParticipantsDelayed() {
crl::on_main(this, [=] { addPreparedParticipants(); });
}
void GroupCall::handleUpdate(const MTPUpdate &update) {
update.match([&](const MTPDupdateGroupCall &data) {
handleUpdate(data);
}, [&](const MTPDupdateGroupCallParticipants &data) {
handleUpdate(data);
}, [](const auto &) {
Unexpected("Type in Instance::applyGroupCallUpdateChecked.");
});
}
void GroupCall::handleUpdate(const MTPDupdateGroupCall &data) {
data.vcall().match([](const MTPDgroupCall &) {
}, [&](const MTPDgroupCallDiscarded &data) {
handlePossibleDiscarded(data);
});
}
void GroupCall::handleUpdate(const MTPDupdateGroupCallParticipants &data) {
const auto callId = data.vcall().match([](const auto &data) {
return data.vid().v;
});
if (_id != callId) {
return;
}
const auto state = _state.current();
if (state != State::Joined && state != State::Connecting) {
return;
@@ -889,18 +928,27 @@ void GroupCall::handleUpdate(const MTPDupdateGroupCallParticipants &data) {
if (data.is_left()) {
if (data.vsource().v == _mySsrc) {
// I was removed from the call, rejoin.
LOG(("Call Info: Rejoin after got 'left' with my ssrc."));
LOG(("Call Info: "
"Rejoin after got 'left' with my ssrc."));
setState(State::Joining);
rejoin();
}
return;
} else if (data.vsource().v != _mySsrc) {
// I joined from another device, hangup.
LOG(("Call Info: Hangup after '!left' with ssrc %1, my %2."
).arg(data.vsource().v
).arg(_mySsrc));
_mySsrc = 0;
hangup();
if (!_mySsrcs.contains(data.vsource().v)) {
// I joined from another device, hangup.
LOG(("Call Info: "
"Hangup after '!left' with ssrc %1, my %2."
).arg(data.vsource().v
).arg(_mySsrc));
_mySsrc = 0;
hangup();
} else {
LOG(("Call Info: "
"Some old 'self' with '!left' and ssrc %1, my %2."
).arg(data.vsource().v
).arg(_mySsrc));
}
return;
}
if (data.is_muted() && !data.is_can_self_unmute()) {
@@ -1087,7 +1135,8 @@ void GroupCall::broadcastPartStart(std::shared_ptr<LoadPartTask> task) {
});
});
}).fail([=](const MTP::Error &error, const MTP::Response &response) {
if (error.type() == u"GROUPCALL_JOIN_MISSING"_q) {
if (error.type() == u"GROUPCALL_JOIN_MISSING"_q
|| error.type() == u"GROUPCALL_FORBIDDEN"_q) {
for (const auto &[task, part] : _broadcastParts) {
_api.request(part.requestId).cancel();
}
@@ -1095,7 +1144,8 @@ void GroupCall::broadcastPartStart(std::shared_ptr<LoadPartTask> task) {
rejoin();
return;
}
const auto status = MTP::IsFloodError(error)
const auto status = (MTP::IsFloodError(error)
|| error.type() == u"TIME_TOO_BIG"_q)
? Status::NotReady
: Status::ResyncNeeded;
finish({

View File

@@ -116,8 +116,8 @@ public:
void rejoinAs(Group::JoinInfo info);
void rejoinWithHash(const QString &hash);
void join(const MTPInputGroupCall &inputCall);
void handleUpdate(const MTPGroupCall &call);
void handleUpdate(const MTPDupdateGroupCallParticipants &data);
void handleUpdate(const MTPUpdate &update);
void handlePossibleCreateOrJoinResponse(const MTPDupdateGroupCall &data);
void changeTitle(const QString &title);
void toggleRecording(bool enabled, const QString &title);
[[nodiscard]] bool recordingStoppedByMe() const {
@@ -227,6 +227,10 @@ private:
RaiseHand,
};
void handlePossibleCreateOrJoinResponse(const MTPDgroupCall &data);
void handlePossibleDiscarded(const MTPDgroupCallDiscarded &data);
void handleUpdate(const MTPDupdateGroupCall &data);
void handleUpdate(const MTPDupdateGroupCallParticipants &data);
void handleRequestError(const MTP::Error &error);
void handleControllerError(const QString &error);
void ensureControllerCreated();
@@ -277,6 +281,7 @@ private:
const not_null<Delegate*> _delegate;
not_null<PeerData*> _peer; // Can change in legacy group migration.
rpl::event_stream<PeerData*> _peerStream;
not_null<History*> _history; // Can change in legacy group migration.
MTP::Sender _api;
rpl::variable<State> _state = State::Creating;
@@ -305,6 +310,7 @@ private:
uint64 _id = 0;
uint64 _accessHash = 0;
uint32 _mySsrc = 0;
base::flat_set<uint32> _mySsrcs;
mtpRequestId _createRequestId = 0;
mtpRequestId _updateMuteRequestId = 0;

View File

@@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "calls/calls_group_call.h"
#include "calls/calls_group_common.h"
#include "calls/calls_group_menu.h"
#include "calls/calls_volume_item.h"
#include "data/data_channel.h"
#include "data/data_chat.h"
@@ -143,9 +144,6 @@ public:
void addActionRipple(QPoint point, Fn<void()> updateCallback) override;
void stopLastActionRipple() override;
int nameIconWidth() const override {
return 0;
}
QSize actionSize() const override {
return QSize(
st::groupCallActiveButton.width,
@@ -1573,7 +1571,7 @@ base::unique_qptr<Ui::PopupMenu> MembersController::createRowContextMenu(
Window::SectionShow::Way::Forward);
});
};
const auto removeFromGroup = crl::guard(this, [=] {
const auto removeFromVoiceChat = crl::guard(this, [=] {
_kickParticipantRequests.fire_copy(participantPeer);
});
@@ -1597,7 +1595,11 @@ base::unique_qptr<Ui::PopupMenu> MembersController::createRowContextMenu(
}
} else {
result->addAction(
tr::lng_context_view_profile(tr::now),
(participantPeer->isUser()
? tr::lng_context_view_profile(tr::now)
: participantPeer->isBroadcast()
? tr::lng_context_view_channel(tr::now)
: tr::lng_context_view_group(tr::now)),
showProfile);
if (participantPeer->isUser()) {
result->addAction(
@@ -1606,9 +1608,7 @@ base::unique_qptr<Ui::PopupMenu> MembersController::createRowContextMenu(
}
const auto canKick = [&] {
const auto user = participantPeer->asUser();
if (!user) {
return false;
} else if (static_cast<Row*>(row.get())->state()
if (static_cast<Row*>(row.get())->state()
== Row::State::Invited) {
return false;
} else if (const auto chat = _peer->asChat()) {
@@ -1616,16 +1616,16 @@ base::unique_qptr<Ui::PopupMenu> MembersController::createRowContextMenu(
|| (user
&& chat->canBanMembers()
&& !chat->admins.contains(user));
} else if (const auto group = _peer->asMegagroup()) {
return group->amCreator()
|| (user && group->canRestrictUser(user));
} else if (const auto channel = _peer->asChannel()) {
return channel->canRestrictParticipant(participantPeer);
}
return false;
}();
if (canKick) {
result->addAction(
tr::lng_context_remove_from_group(tr::now),
removeFromGroup);
result->addAction(MakeAttentionAction(
result->menu(),
tr::lng_group_call_context_remove(tr::now),
removeFromVoiceChat));
}
}
if (result->empty()) {

View File

@@ -517,16 +517,10 @@ base::unique_qptr<Ui::Menu::ItemBase> MakeRecordingAction(
base::unique_qptr<Ui::Menu::ItemBase> MakeFinishAction(
not_null<Ui::Menu::Menu*> menu,
Fn<void()> callback) {
return base::make_unique_q<Ui::Menu::Action>(
return MakeAttentionAction(
menu,
st::groupCallFinishMenu,
Ui::Menu::CreateAction(
menu,
tr::lng_group_call_end(tr::now),
std::move(callback)),
nullptr,
nullptr);
tr::lng_group_call_end(tr::now),
std::move(callback));
}
} // namespace
@@ -675,6 +669,21 @@ void FillMenu(
BoxContext::GroupCallPanel));
}
}));
}
base::unique_qptr<Ui::Menu::ItemBase> MakeAttentionAction(
not_null<Ui::Menu::Menu*> menu,
const QString &text,
Fn<void()> callback) {
return base::make_unique_q<Ui::Menu::Action>(
menu,
st::groupCallFinishMenu,
Ui::Menu::CreateAction(
menu,
text,
std::move(callback)),
nullptr,
nullptr);
}

View File

@@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#pragma once
#include "base/object_ptr.h"
#include "base/unique_qptr.h"
namespace Ui {
class DropdownMenu;
@@ -15,6 +16,11 @@ class GenericBox;
class BoxContent;
} // namespace Ui
namespace Ui::Menu {
class ItemBase;
class Menu;
} // namespace Ui::Menu
namespace Calls {
class GroupCall;
} // namespace Calls
@@ -45,4 +51,9 @@ void FillMenu(
Fn<void()> chooseJoinAs,
Fn<void(object_ptr<Ui::BoxContent>)> showBox);
[[nodiscard]] base::unique_qptr<Ui::Menu::ItemBase> MakeAttentionAction(
not_null<Ui::Menu::Menu*> menu,
const QString &text,
Fn<void()> callback);
} // namespace Calls::Group

View File

@@ -23,7 +23,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/layers/layer_manager.h"
#include "ui/layers/generic_box.h"
#include "ui/text/text_utilities.h"
#include "ui/toast/toast.h"
#include "ui/toasts/common_toasts.h"
#include "ui/special_buttons.h"
#include "info/profile/info_profile_values.h" // Info::Profile::Value.
@@ -82,7 +81,7 @@ private:
[[nodiscard]] bool isAlreadyIn(not_null<UserData*> user) const;
std::unique_ptr<PeerListRow> createRow(
not_null<UserData*> user) const override;
not_null<PeerData*> participant) const override;
not_null<PeerData*> _peer;
const base::flat_set<not_null<UserData*>> _alreadyIn;
@@ -190,8 +189,9 @@ bool InviteController::isAlreadyIn(not_null<UserData*> user) const {
}
std::unique_ptr<PeerListRow> InviteController::createRow(
not_null<UserData*> user) const {
if (user->isSelf() || user->isBot()) {
not_null<PeerData*> participant) const {
const auto user = participant->asUser();
if (!user || user->isSelf() || user->isBot()) {
return nullptr;
}
auto result = std::make_unique<PeerListRow>(user);
@@ -291,9 +291,10 @@ Panel::Panel(not_null<GroupCall*> call)
call->allowedToSpeakNotifications(
) | rpl::start_with_next([=] {
if (isActive()) {
Ui::Toast::Show(
widget(),
tr::lng_group_call_can_speak_here(tr::now));
Ui::ShowMultilineToast({
.parentOverride = widget(),
.text = { tr::lng_group_call_can_speak_here(tr::now) },
});
} else {
const auto real = _peer->groupCall();
const auto name = (real
@@ -301,13 +302,12 @@ Panel::Panel(not_null<GroupCall*> call)
&& !real->title().isEmpty())
? real->title()
: _peer->name;
Ui::Toast::Show(Ui::Toast::Config{
Ui::ShowMultilineToast({
.text = tr::lng_group_call_can_speak(
tr::now,
lt_chat,
Ui::Text::Bold(name),
Ui::Text::WithEntities),
.st = &st::defaultToast,
});
}
}, widget()->lifetime());
@@ -529,16 +529,17 @@ void Panel::initWithCall(GroupCall *call) {
_members->kickParticipantRequests(
) | rpl::start_with_next([=](not_null<PeerData*> participantPeer) {
if (const auto user = participantPeer->asUser()) {
kickMember(user);
}
kickParticipant(participantPeer);
}, _callLifetime);
const auto showBox = [=](object_ptr<Ui::BoxContent> next) {
_layerBg->showBox(std::move(next));
};
const auto showToast = [=](QString text) {
Ui::Toast::Show(widget(), text);
Ui::ShowMultilineToast({
.parentOverride = widget(),
.text = { text },
});
};
auto [shareLinkCallback, shareLinkLifetime] = ShareInviteLinkAction(
_peer,
@@ -609,13 +610,13 @@ void Panel::setupJoinAsChangedToasts() {
return (state == State::Joined);
}) | rpl::take(1);
}) | rpl::flatten_latest() | rpl::start_with_next([=] {
Ui::ShowMultilineToast(Ui::MultilineToastArgs{
Ui::ShowMultilineToast({
.parentOverride = widget(),
.text = tr::lng_group_call_join_as_changed(
tr::now,
lt_name,
Ui::Text::Bold(_call->joinAs()->name),
Ui::Text::WithEntities),
.parentOverride = widget(),
});
}, widget()->lifetime());
}
@@ -629,13 +630,13 @@ void Panel::setupTitleChangedToasts() {
? _peer->name
: _peer->groupCall()->title();
}) | rpl::start_with_next([=](const QString &title) {
Ui::ShowMultilineToast(Ui::MultilineToastArgs{
Ui::ShowMultilineToast({
.parentOverride = widget(),
.text = tr::lng_group_call_title_changed(
tr::now,
lt_title,
Ui::Text::Bold(title),
Ui::Text::WithEntities),
.parentOverride = widget(),
});
}, widget()->lifetime());
}
@@ -659,9 +660,10 @@ void Panel::subscribeToChanges(not_null<Data::GroupCall*> real) {
const auto skip = st::groupCallRecordingMarkSkip;
_recordingMark->resize(size + 2 * skip, size + 2 * skip);
_recordingMark->setClickedCallback([=] {
Ui::Toast::Show(
widget(),
tr::lng_group_call_is_recorded(tr::now));
Ui::ShowMultilineToast({
.parentOverride = widget(),
.text = { tr::lng_group_call_is_recorded(tr::now) },
});
});
const auto animate = [=] {
const auto opaque = state->opaque;
@@ -697,13 +699,16 @@ void Panel::subscribeToChanges(not_null<Data::GroupCall*> real) {
) | rpl::distinct_until_changed(
) | rpl::start_with_next([=](bool recorded) {
validateRecordingMark(recorded);
Ui::Toast::Show(
widget(),
(recorded
? tr::lng_group_call_recording_started(tr::now)
Ui::ShowMultilineToast({
.parentOverride = widget(),
.text = (recorded
? tr::lng_group_call_recording_started
: (_call && _call->recordingStoppedByMe())
? tr::lng_group_call_recording_saved(tr::now)
: tr::lng_group_call_recording_stopped(tr::now)));
? tr::lng_group_call_recording_saved
: tr::lng_group_call_recording_stopped)(
tr::now,
Ui::Text::RichLangValue),
});
}, widget()->lifetime());
validateRecordingMark(real->recordStartDate() != 0);
@@ -755,7 +760,10 @@ void Panel::chooseJoinAs() {
_layerBg->showBox(std::move(next));
};
const auto showToast = [=](QString text) {
Ui::Toast::Show(widget(), text);
Ui::ShowMultilineToast({
.parentOverride = widget(),
.text = { text },
});
};
_joinAsProcess.start(
_peer,
@@ -849,28 +857,24 @@ void Panel::addMembers() {
}
const auto result = call->inviteUsers(users);
if (const auto user = std::get_if<not_null<UserData*>>(&result)) {
Ui::Toast::Show(
widget(),
Ui::Toast::Config{
.text = tr::lng_group_call_invite_done_user(
tr::now,
lt_user,
Ui::Text::Bold((*user)->firstName),
Ui::Text::WithEntities),
.st = &st::defaultToast,
});
Ui::ShowMultilineToast({
.parentOverride = widget(),
.text = tr::lng_group_call_invite_done_user(
tr::now,
lt_user,
Ui::Text::Bold((*user)->firstName),
Ui::Text::WithEntities),
});
} else if (const auto count = std::get_if<int>(&result)) {
if (*count > 0) {
Ui::Toast::Show(
widget(),
Ui::Toast::Config{
.text = tr::lng_group_call_invite_done_many(
tr::now,
lt_count,
*count,
Ui::Text::RichLangValue),
.st = &st::defaultToast,
});
Ui::ShowMultilineToast({
.parentOverride = widget(),
.text = tr::lng_group_call_invite_done_many(
tr::now,
lt_count,
*count,
Ui::Text::RichLangValue),
});
}
} else {
Unexpected("Result in GroupCall::inviteUsers.");
@@ -954,15 +958,22 @@ void Panel::addMembers() {
_layerBg->showBox(Box<PeerListsBox>(std::move(controllers), initBox));
}
void Panel::kickMember(not_null<UserData*> user) {
void Panel::kickParticipant(not_null<PeerData*> participantPeer) {
_layerBg->showBox(Box([=](not_null<Ui::GenericBox*> box) {
box->addRow(
object_ptr<Ui::FlatLabel>(
box.get(),
tr::lng_profile_sure_kick(
tr::now,
lt_user,
user->firstName),
(!participantPeer->isUser()
? tr::lng_group_call_remove_channel(
tr::now,
lt_channel,
participantPeer->name)
: (_peer->isBroadcast()
? tr::lng_profile_sure_kick_channel
: tr::lng_profile_sure_kick)(
tr::now,
lt_user,
participantPeer->asUser()->firstName)),
st::groupCallBoxLabel),
style::margins(
st::boxRowPadding.left(),
@@ -971,26 +982,29 @@ void Panel::kickMember(not_null<UserData*> user) {
st::boxPadding.bottom()));
box->addButton(tr::lng_box_remove(), [=] {
box->closeBox();
kickMemberSure(user);
kickParticipantSure(participantPeer);
});
box->addButton(tr::lng_cancel(), [=] { box->closeBox(); });
}));
}
void Panel::kickMemberSure(not_null<UserData*> user) {
void Panel::kickParticipantSure(not_null<PeerData*> participantPeer) {
if (const auto chat = _peer->asChat()) {
chat->session().api().kickParticipant(chat, user);
chat->session().api().kickParticipant(chat, participantPeer);
} else if (const auto channel = _peer->asChannel()) {
const auto currentRestrictedRights = [&]() -> MTPChatBannedRights {
const auto it = channel->mgInfo->lastRestricted.find(user);
return (it != channel->mgInfo->lastRestricted.cend())
? it->second.rights
: MTP_chatBannedRights(MTP_flags(0), MTP_int(0));
const auto currentRestrictedRights = [&] {
const auto user = participantPeer->asUser();
if (!channel->mgInfo || !user) {
return ChannelData::EmptyRestrictedRights(participantPeer);
}
const auto i = channel->mgInfo->lastRestricted.find(user);
return (i != channel->mgInfo->lastRestricted.cend())
? i->second.rights
: ChannelData::EmptyRestrictedRights(participantPeer);
}();
channel->session().api().kickParticipant(
channel,
user,
participantPeer,
currentRestrictedRights);
}
}

View File

@@ -89,8 +89,8 @@ private:
void showMainMenu();
void chooseJoinAs();
void addMembers();
void kickMember(not_null<UserData*> user);
void kickMemberSure(not_null<UserData*> user);
void kickParticipant(not_null<PeerData*> participantPeer);
void kickParticipantSure(not_null<PeerData*> participantPeer);
[[nodiscard]] QRect computeTitleRect() const;
void refreshTitle();
void refreshTitleGeometry();

View File

@@ -19,7 +19,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/widgets/input_fields.h"
#include "ui/wrap/slide_wrap.h"
#include "ui/text/text_utilities.h"
#include "ui/toast/toast.h"
#include "ui/toasts/common_toasts.h"
#include "lang/lang_keys.h"
#include "boxes/share_box.h"
#include "history/history_message.h" // GetErrorTextForSending.
@@ -535,9 +535,10 @@ void SettingsBox(
box->getDelegate()->show(std::move(next));
});
const auto showToast = crl::guard(box, [=](QString text) {
Ui::Toast::Show(
box->getDelegate()->outerContainer(),
text);
Ui::ShowMultilineToast({
.parentOverride = box->getDelegate()->outerContainer(),
.text = { text },
});
});
auto [shareLinkCallback, shareLinkLifetime] = ShareInviteLinkAction(
peer,
@@ -572,9 +573,10 @@ void SettingsBox(
}
QGuiApplication::clipboard()->setText(link);
if (weakBox) {
Ui::Toast::Show(
box->getDelegate()->outerContainer(),
tr::lng_create_channel_link_copied(tr::now));
Ui::ShowMultilineToast({
.parentOverride = box->getDelegate()->outerContainer(),
.text = { tr::lng_create_channel_link_copied(tr::now) },
});
}
return true;
};

View File

@@ -427,28 +427,23 @@ void Instance::handleGroupCallUpdate(
} else {
applyGroupCallUpdateChecked(session, update);
}
if (_currentGroupCall
&& (&_currentGroupCall->peer()->session() == session)) {
update.match([&](const MTPDupdateGroupCall &data) {
_currentGroupCall->handlePossibleCreateOrJoinResponse(data);
}, [](const auto &) {
});
}
}
void Instance::applyGroupCallUpdateChecked(
not_null<Main::Session*> session,
const MTPUpdate &update) {
if (!_currentGroupCall
|| (&_currentGroupCall->peer()->session() != session)) {
return;
if (_currentGroupCall
&& (&_currentGroupCall->peer()->session() == session)) {
_currentGroupCall->handleUpdate(update);
}
update.match([&](const MTPDupdateGroupCall &data) {
_currentGroupCall->handleUpdate(data.vcall());
}, [&](const MTPDupdateGroupCallParticipants &data) {
const auto callId = data.vcall().match([](const auto &data) {
return data.vid().v;
});
if (_currentGroupCall->id() == callId) {
_currentGroupCall->handleUpdate(data);
}
}, [](const auto &) {
Unexpected("Type in Instance::applyGroupCallUpdateChecked.");
});
}
void Instance::handleSignalingData(

View File

@@ -133,6 +133,20 @@ std::map<int, const char*> BetaLogs() {
"- Make default interface scale 110% on macOS Retina screens.\n"
},
{
2006005,
"- Improvements and fixes in new voice chat features.\n"
},
{
2006007,
"- Improve voice chat participants list updating.\n"
},
{
2006008,
"- Fix connecting and getting allowed to speak on voice chats.\n"
"- MPRIS support on Linux.\n"
},
};
};

View File

@@ -22,7 +22,7 @@ constexpr auto AppId = "{53F49750-6209-4FBF-9CA8-7A333C87D1ED}"_cs;
constexpr auto AppNameOld = "Telegram Win (Unofficial)"_cs;
constexpr auto AppName = "Telegram Desktop"_cs;
constexpr auto AppFile = "Telegram"_cs;
constexpr auto AppVersion = 2006004;
constexpr auto AppVersionStr = "2.6.4";
constexpr auto AppBetaVersion = true;
constexpr auto AppVersion = 2007001;
constexpr auto AppVersionStr = "2.7.1";
constexpr auto AppBetaVersion = false;
constexpr auto AppAlphaVersion = TDESKTOP_ALPHA_VERSION;

View File

@@ -188,7 +188,13 @@ void ChannelData::setKickedCount(int newKickedCount) {
}
}
MTPChatBannedRights ChannelData::KickedRestrictedRights() {
MTPChatBannedRights ChannelData::EmptyRestrictedRights(
not_null<PeerData*> participant) {
return MTP_chatBannedRights(MTP_flags(0), MTP_int(0));
}
MTPChatBannedRights ChannelData::KickedRestrictedRights(
not_null<PeerData*> participant) {
using Flag = MTPDchatBannedRights::Flag;
const auto flags = Flag::f_view_messages
| Flag::f_send_messages
@@ -199,7 +205,7 @@ MTPChatBannedRights ChannelData::KickedRestrictedRights() {
| Flag::f_send_games
| Flag::f_send_inline;
return MTP_chatBannedRights(
MTP_flags(flags),
MTP_flags(participant->isUser() ? flags : Flag::f_view_messages),
MTP_int(std::numeric_limits<int32>::max()));
}
@@ -267,11 +273,17 @@ void ChannelData::applyEditAdmin(
session().changes().peerUpdated(this, UpdateFlag::Admins);
}
void ChannelData::applyEditBanned(not_null<UserData*> user, const MTPChatBannedRights &oldRights, const MTPChatBannedRights &newRights) {
void ChannelData::applyEditBanned(
not_null<PeerData*> participant,
const MTPChatBannedRights &oldRights,
const MTPChatBannedRights &newRights) {
auto flags = UpdateFlag::BannedUsers | UpdateFlag::None;
auto isKicked = (newRights.c_chatBannedRights().vflags().v & MTPDchatBannedRights::Flag::f_view_messages);
auto isRestricted = !isKicked && (newRights.c_chatBannedRights().vflags().v != 0);
if (mgInfo) {
auto isKicked = Data::ChatBannedRightsFlags(newRights)
& ChatRestriction::f_view_messages;
auto isRestricted = !isKicked
&& (Data::ChatBannedRightsFlags(newRights) != 0);
const auto user = participant->asUser();
if (mgInfo && user) {
// If rights are empty - still remove admin? TODO check
if (mgInfo->lastAdmins.contains(user)) {
mgInfo->lastAdmins.remove(user);
@@ -284,7 +296,9 @@ void ChannelData::applyEditBanned(not_null<UserData*> user, const MTPChatBannedR
auto it = mgInfo->lastRestricted.find(user);
if (isRestricted) {
if (it == mgInfo->lastRestricted.cend()) {
mgInfo->lastRestricted.emplace(user, MegagroupInfo::Restricted { newRights });
mgInfo->lastRestricted.emplace(
user,
MegagroupInfo::Restricted { newRights });
setRestrictedCount(restrictedCount() + 1);
} else {
it->second.rights = newRights;
@@ -297,7 +311,9 @@ void ChannelData::applyEditBanned(not_null<UserData*> user, const MTPChatBannedR
}
}
if (isKicked) {
auto i = ranges::find(mgInfo->lastParticipants, user);
auto i = ranges::find(
mgInfo->lastParticipants,
not_null{ user });
if (i != mgInfo->lastParticipants.end()) {
mgInfo->lastParticipants.erase(i);
}
@@ -319,9 +335,9 @@ void ChannelData::applyEditBanned(not_null<UserData*> user, const MTPChatBannedR
}
}
Data::ChannelAdminChanges(this).remove(peerToUser(user->id));
} else {
} else if (!mgInfo) {
if (isKicked) {
if (membersCount() > 1) {
if (user && membersCount() > 1) {
setMembersCount(membersCount() - 1);
flags |= UpdateFlag::Members;
}
@@ -484,7 +500,7 @@ bool ChannelData::canDelete() const {
}
bool ChannelData::canEditLastAdmin(not_null<UserData*> user) const {
// Duplicated in ParticipantsBoxController::canEditAdmin :(
// Duplicated in ParticipantsAdditionalData::canEditAdmin :(
if (mgInfo) {
auto i = mgInfo->lastAdmins.find(user);
if (i != mgInfo->lastAdmins.cend()) {
@@ -496,7 +512,7 @@ bool ChannelData::canEditLastAdmin(not_null<UserData*> user) const {
}
bool ChannelData::canEditAdmin(not_null<UserData*> user) const {
// Duplicated in ParticipantsBoxController::canEditAdmin :(
// Duplicated in ParticipantsAdditionalData::canEditAdmin :(
if (user->isSelf()) {
return false;
} else if (amCreator()) {
@@ -507,14 +523,17 @@ bool ChannelData::canEditAdmin(not_null<UserData*> user) const {
return adminRights() & AdminRight::f_add_admins;
}
bool ChannelData::canRestrictUser(not_null<UserData*> user) const {
// Duplicated in ParticipantsBoxController::canRestrictUser :(
if (user->isSelf()) {
bool ChannelData::canRestrictParticipant(
not_null<PeerData*> participant) const {
// Duplicated in ParticipantsAdditionalData::canRestrictParticipant :(
if (participant->isSelf()) {
return false;
} else if (amCreator()) {
return true;
} else if (!canEditLastAdmin(user)) {
return false;
} else if (const auto user = participant->asUser()) {
if (!canEditLastAdmin(user)) {
return false;
}
}
return adminRights() & AdminRight::f_ban_users;
}
@@ -543,12 +562,14 @@ void ChannelData::setAdminRights(const MTPChatAdminRights &rights) {
}
void ChannelData::setRestrictions(const MTPChatBannedRights &rights) {
if (rights.c_chatBannedRights().vflags().v == restrictions()
&& rights.c_chatBannedRights().vuntil_date().v == _restrictedUntil) {
const auto restrictedFlags = Data::ChatBannedRightsFlags(rights);
const auto restrictedUntilDate = Data::ChatBannedRightsUntilDate(rights);
if (restrictedFlags == restrictions()
&& restrictedUntilDate == _restrictedUntil) {
return;
}
_restrictedUntil = rights.c_chatBannedRights().vuntil_date().v;
_restrictions.set(rights.c_chatBannedRights().vflags().v);
_restrictedUntil = restrictedUntilDate;
_restrictions.set(restrictedFlags);
if (isMegagroup()) {
const auto self = session().user();
if (hasRestrictions()) {
@@ -568,10 +589,11 @@ void ChannelData::setRestrictions(const MTPChatBannedRights &rights) {
}
void ChannelData::setDefaultRestrictions(const MTPChatBannedRights &rights) {
if (rights.c_chatBannedRights().vflags().v == defaultRestrictions()) {
const auto restrictionFlags = Data::ChatBannedRightsFlags(rights);
if (restrictionFlags == defaultRestrictions()) {
return;
}
_defaultRestrictions.set(rights.c_chatBannedRights().vflags().v);
_defaultRestrictions.set(restrictionFlags);
session().changes().peerUpdated(this, UpdateFlag::Rights);
}
@@ -903,8 +925,13 @@ void ApplyMegagroupAdmins(
auto admins = ranges::make_subrange(
list.begin(), list.end()
) | ranges::views::transform([](const MTPChannelParticipant &p) {
const auto userId = p.match([](const auto &data) {
return data.vuser_id().v;
const auto participantId = p.match([](
const MTPDchannelParticipantBanned &data) {
return peerFromMTP(data.vpeer());
}, [](const MTPDchannelParticipantLeft &data) {
return peerFromMTP(data.vpeer());
}, [](const auto &data) {
return peerFromUser(data.vuser_id());
});
const auto rank = p.match([](const MTPDchannelParticipantAdmin &data) {
return qs(data.vrank().value_or_empty());
@@ -913,10 +940,13 @@ void ApplyMegagroupAdmins(
}, [](const auto &data) {
return QString();
});
return std::make_pair(userId, rank);
return std::make_pair(participantId, rank);
}) | ranges::views::filter([](const auto &pair) {
return peerIsUser(pair.first);
});
for (const auto &[userId, rank] : admins) {
adding.emplace(userId, rank);
for (const auto &[participantId, rank] : admins) {
Assert(peerIsUser(participantId));
adding.emplace(peerToUser(participantId), rank);
}
if (channel->mgInfo->creator) {
adding.emplace(

View File

@@ -209,7 +209,10 @@ public:
return flags() & MTPDchannel::Flag::f_fake;
}
static MTPChatBannedRights KickedRestrictedRights();
static MTPChatBannedRights EmptyRestrictedRights(
not_null<PeerData*> participant);
static MTPChatBannedRights KickedRestrictedRights(
not_null<PeerData*> participant);
static constexpr auto kRestrictUntilForever = TimeId(INT_MAX);
[[nodiscard]] static bool IsRestrictedForever(TimeId until) {
return !until || (until == kRestrictUntilForever);
@@ -220,7 +223,7 @@ public:
const MTPChatAdminRights &newRights,
const QString &rank);
void applyEditBanned(
not_null<UserData*> user,
not_null<PeerData*> participant,
const MTPChatBannedRights &oldRights,
const MTPChatBannedRights &newRights);
@@ -310,7 +313,8 @@ public:
[[nodiscard]] bool canEditStickers() const;
[[nodiscard]] bool canDelete() const;
[[nodiscard]] bool canEditAdmin(not_null<UserData*> user) const;
[[nodiscard]] bool canRestrictUser(not_null<UserData*> user) const;
[[nodiscard]] bool canRestrictParticipant(
not_null<PeerData*> participant) const;
void setInviteLink(const QString &newInviteLink);
[[nodiscard]] QString inviteLink() const {

View File

@@ -52,7 +52,8 @@ auto ChatData::defaultAdminRights(not_null<UserData*> user) -> AdminRights {
const auto isCreator = (creator == user->bareId())
|| (user->isSelf() && amCreator());
using Flag = AdminRight;
return Flag::f_change_info
return Flag::f_other
| Flag::f_change_info
| Flag::f_delete_messages
| Flag::f_ban_users
| Flag::f_invite_users
@@ -155,10 +156,11 @@ void ChatData::setAdminRights(const MTPChatAdminRights &rights) {
}
void ChatData::setDefaultRestrictions(const MTPChatBannedRights &rights) {
if (rights.c_chatBannedRights().vflags().v == defaultRestrictions()) {
const auto restrictionFlags = Data::ChatBannedRightsFlags(rights);
if (restrictionFlags == defaultRestrictions()) {
return;
}
_defaultRestrictions.set(rights.c_chatBannedRights().vflags().v);
_defaultRestrictions.set(restrictionFlags);
session().changes().peerUpdated(this, UpdateFlag::Rights);
}

View File

@@ -27,6 +27,12 @@ constexpr auto kSpeakingAfterActive = crl::time(6000);
constexpr auto kActiveAfterJoined = crl::time(1000);
constexpr auto kWaitForUpdatesTimeout = 3 * crl::time(1000);
[[nodiscard]] QString ExtractNextOffset(const MTPphone_GroupCall &call) {
return call.match([&](const MTPDphone_groupCall &data) {
return qs(data.vparticipants_next_offset());
});
}
} // namespace
GroupCall::GroupCall(
@@ -50,6 +56,10 @@ uint64 GroupCall::id() const {
return _id;
}
bool GroupCall::loaded() const {
return _version > 0;
}
not_null<PeerData*> GroupCall::peer() const {
return _peer;
}
@@ -71,18 +81,24 @@ auto GroupCall::participants() const
}
void GroupCall::requestParticipants() {
if (_participantsRequestId || _reloadRequestId) {
return;
} else if (_allParticipantsLoaded) {
return;
if (!_savedFull) {
if (_participantsRequestId || _reloadRequestId) {
return;
} else if (_allParticipantsLoaded) {
return;
}
}
_participantsRequestId = api().request(MTPphone_GetGroupParticipants(
input(),
MTP_vector<MTPInputPeer>(), // ids
MTP_vector<MTPint>(), // ssrcs
MTP_string(_nextOffset),
MTP_string(_savedFull
? ExtractNextOffset(*_savedFull)
: _nextOffset),
MTP_int(kRequestPerPage)
)).done([=](const MTPphone_GroupParticipants &result) {
_participantsRequestId = 0;
processSavedFullCall();
result.match([&](const MTPDphone_groupParticipants &data) {
_nextOffset = qs(data.vnext_offset());
_peer->owner().processUsers(data.vusers());
@@ -94,20 +110,31 @@ void GroupCall::requestParticipants() {
if (data.vparticipants().v.isEmpty()) {
_allParticipantsLoaded = true;
}
computeParticipantsCount();
_participantsSliceAdded.fire({});
_participantsRequestId = 0;
processQueuedUpdates();
finishParticipantsSliceRequest();
});
}).fail([=](const MTP::Error &error) {
_participantsRequestId = 0;
processSavedFullCall();
setServerParticipantsCount(_participants.size());
_allParticipantsLoaded = true;
computeParticipantsCount();
_participantsRequestId = 0;
processQueuedUpdates();
finishParticipantsSliceRequest();
}).send();
}
void GroupCall::processSavedFullCall() {
if (!_savedFull) {
return;
}
_reloadRequestId = 0;
processFullCallFields(*base::take(_savedFull));
}
void GroupCall::finishParticipantsSliceRequest() {
computeParticipantsCount();
processQueuedUpdates();
_participantsSliceAdded.fire({});
}
void GroupCall::setServerParticipantsCount(int count) {
_serverParticipantsCount = count;
changePeerEmptyCallFlag();
@@ -175,13 +202,23 @@ void GroupCall::enqueueUpdate(const MTPUpdate &update) {
update.match([&](const MTPDupdateGroupCall &updateData) {
updateData.vcall().match([&](const MTPDgroupCall &data) {
const auto version = data.vversion().v;
if (!_version || _version == version) {
applyUpdate(update);
} else if (_version < version) {
_queuedUpdates.emplace(std::pair{ version, false }, update);
if (!_applyingQueuedUpdates
&& (!_version || _version == version)) {
DEBUG_LOG(("Group Call Participants: "
"Apply updateGroupCall %1 -> %2"
).arg(_version
).arg(version));
applyEnqueuedUpdate(update);
} else if (!_version || _version <= version) {
DEBUG_LOG(("Group Call Participants: "
"Queue updateGroupCall %1 -> %2"
).arg(_version
).arg(version));
const auto type = QueuedType::Call;
_queuedUpdates.emplace(std::pair{ version, type }, update);
}
}, [&](const MTPDgroupCallDiscarded &data) {
applyUpdate(update);
discard(data);
});
}, [&](const MTPDupdateGroupCallParticipants &updateData) {
const auto version = updateData.vversion().v;
@@ -195,10 +232,22 @@ void GroupCall::enqueueUpdate(const MTPUpdate &update) {
true,
proj);
const auto required = increment ? (version - 1) : version;
if (_version == required) {
applyUpdate(update);
} else if (_version < required) {
_queuedUpdates.emplace(std::pair{ version, increment }, update);
if (!_applyingQueuedUpdates && (_version == required)) {
DEBUG_LOG(("Group Call Participants: "
"Apply updateGroupCallParticipant %1 (%2)"
).arg(_version
).arg(Logs::b(increment)));
applyEnqueuedUpdate(update);
} else if (_version <= required) {
DEBUG_LOG(("Group Call Participants: "
"Queue updateGroupCallParticipant %1 -> %2 (%3)"
).arg(_version
).arg(version
).arg(Logs::b(increment)));
const auto type = increment
? QueuedType::VersionedParticipant
: QueuedType::Participant;
_queuedUpdates.emplace(std::pair{ version, type }, update);
}
}, [](const auto &) {
Unexpected("Type in GroupCall::enqueueUpdate.");
@@ -206,7 +255,7 @@ void GroupCall::enqueueUpdate(const MTPUpdate &update) {
processQueuedUpdates();
}
void GroupCall::discard() {
void GroupCall::discard(const MTPDgroupCallDiscarded &data) {
const auto id = _id;
const auto peer = _peer;
crl::on_main(&peer->session(), [=] {
@@ -218,22 +267,28 @@ void GroupCall::discard() {
}
}
});
Core::App().calls().applyGroupCallUpdateChecked(
&peer->session(),
MTP_updateGroupCall(
MTP_int(peer->bareId()),
MTP_groupCallDiscarded(
data.vid(),
data.vaccess_hash(),
data.vduration())));
}
void GroupCall::processFullCall(const MTPphone_GroupCall &call) {
void GroupCall::processFullCallUsersChats(const MTPphone_GroupCall &call) {
call.match([&](const MTPDphone_groupCall &data) {
_peer->owner().processUsers(data.vusers());
_peer->owner().processChats(data.vchats());
});
}
void GroupCall::processFullCallFields(const MTPphone_GroupCall &call) {
call.match([&](const MTPDphone_groupCall &data) {
const auto &participants = data.vparticipants().v;
const auto nextOffset = qs(data.vparticipants_next_offset());
data.vcall().match([&](const MTPDgroupCall &data) {
if (data.vversion().v == _version
&& data.vparticipants_count().v == _serverParticipantsCount
&& (_serverParticipantsCount >= _participants.size())
&& (!_allParticipantsLoaded
|| _serverParticipantsCount == _participants.size())) {
return;
}
_participants.clear();
_speakingByActiveFinishes.clear();
_participantPeerBySsrc.clear();
@@ -245,16 +300,23 @@ void GroupCall::processFullCall(const MTPphone_GroupCall &call) {
_nextOffset = nextOffset;
applyCallFields(data);
_participantsSliceAdded.fire({});
}, [&](const MTPDgroupCallDiscarded &data) {
discard();
discard(data);
});
processQueuedUpdates();
});
}
void GroupCall::processFullCall(const MTPphone_GroupCall &call) {
processFullCallUsersChats(call);
processFullCallFields(call);
finishParticipantsSliceRequest();
}
void GroupCall::applyCallFields(const MTPDgroupCall &data) {
DEBUG_LOG(("Group Call Participants: "
"Set from groupCall %1 -> %2"
).arg(_version
).arg(data.vversion().v));
_version = data.vversion().v;
if (!_version) {
LOG(("API Error: Got zero version in groupCall."));
@@ -269,8 +331,6 @@ void GroupCall::applyCallFields(const MTPDgroupCall &data) {
_recordStartDate = data.vrecord_start_date().value_or_empty();
_allParticipantsLoaded
= (_serverParticipantsCount == _participants.size());
computeParticipantsCount();
processQueuedUpdates();
}
void GroupCall::applyLocalUpdate(
@@ -280,14 +340,24 @@ void GroupCall::applyLocalUpdate(
ApplySliceSource::UpdateReceived);
}
void GroupCall::applyUpdate(const MTPUpdate &update) {
void GroupCall::applyEnqueuedUpdate(const MTPUpdate &update) {
Expects(!_applyingQueuedUpdates);
_applyingQueuedUpdates = true;
const auto guard = gsl::finally([&] { _applyingQueuedUpdates = false; });
update.match([&](const MTPDupdateGroupCall &data) {
data.vcall().match([&](const MTPDgroupCall &data) {
applyCallFields(data);
computeParticipantsCount();
}, [&](const MTPDgroupCallDiscarded &data) {
discard();
discard(data);
});
}, [&](const MTPDupdateGroupCallParticipants &data) {
DEBUG_LOG(("Group Call Participants: "
"Set from updateGroupCallParticipants %1 -> %2"
).arg(_version
).arg(data.vversion().v));
_version = data.vversion().v;
if (!_version) {
LOG(("API Error: "
@@ -298,7 +368,7 @@ void GroupCall::applyUpdate(const MTPUpdate &update) {
data.vparticipants().v,
ApplySliceSource::UpdateReceived);
}, [](const auto &) {
Unexpected("Type in GroupCall::processQueuedUpdates.");
Unexpected("Type in GroupCall::applyEnqueuedUpdate.");
});
Core::App().calls().applyGroupCallUpdateChecked(
&_peer->session(),
@@ -306,7 +376,7 @@ void GroupCall::applyUpdate(const MTPUpdate &update) {
}
void GroupCall::processQueuedUpdates() {
if (!_version) {
if (!_version || _applyingQueuedUpdates) {
return;
}
@@ -314,26 +384,22 @@ void GroupCall::processQueuedUpdates() {
while (!_queuedUpdates.empty()) {
const auto &entry = _queuedUpdates.front();
const auto version = entry.first.first;
const auto versionIncremented = entry.first.second;
const auto type = entry.first.second;
const auto incremented = (type == QueuedType::VersionedParticipant);
if ((version < _version)
|| (version == _version && versionIncremented)) {
|| (version == _version && incremented)) {
_queuedUpdates.erase(_queuedUpdates.begin());
} else if (version == _version
|| (version == _version + 1 && versionIncremented)) {
|| (version == _version + 1 && incremented)) {
const auto update = entry.second;
_queuedUpdates.erase(_queuedUpdates.begin());
applyUpdate(update);
applyEnqueuedUpdate(update);
} else {
break;
}
}
if (_queuedUpdates.empty()) {
const auto server = _serverParticipantsCount;
const auto local = int(_participants.size());
if (server < local
|| (_allParticipantsLoaded && server > local)) {
reload();
}
_reloadByQueuedUpdatesTimer.cancel();
} else if (_queuedUpdates.size() != size
|| !_reloadByQueuedUpdatesTimer.isActive()) {
_reloadByQueuedUpdatesTimer.callOnce(kWaitForUpdatesTimeout);
@@ -347,26 +413,54 @@ void GroupCall::computeParticipantsCount() {
}
void GroupCall::reload() {
if (_reloadRequestId) {
if (_reloadRequestId || _applyingQueuedUpdates) {
return;
} else if (_participantsRequestId) {
api().request(_participantsRequestId).cancel();
_participantsRequestId = 0;
}
_queuedUpdates.clear();
DEBUG_LOG(("Group Call Participants: "
"Reloading with queued: %1"
).arg(_queuedUpdates.size()));
while (!_queuedUpdates.empty()) {
const auto &entry = _queuedUpdates.front();
const auto update = entry.second;
_queuedUpdates.erase(_queuedUpdates.begin());
applyEnqueuedUpdate(update);
}
_reloadByQueuedUpdatesTimer.cancel();
_reloadRequestId = api().request(
MTPphone_GetGroupCall(input())
).done([=](const MTPphone_GroupCall &result) {
processFullCall(result);
if (requestParticipantsAfterReload(result)) {
_savedFull = result;
processFullCallUsersChats(result);
requestParticipants();
return;
}
_reloadRequestId = 0;
processFullCall(result);
}).fail([=](const MTP::Error &error) {
_reloadRequestId = 0;
}).send();
}
bool GroupCall::requestParticipantsAfterReload(
const MTPphone_GroupCall &call) const {
return call.match([&](const MTPDphone_groupCall &data) {
const auto received = data.vparticipants().v.size();
const auto size = data.vcall().match([&](const MTPDgroupCall &data) {
return data.vparticipants_count().v;
}, [](const auto &) {
return 0;
});
return (received < size) && (received < _participants.size());
});
}
void GroupCall::applyParticipantsSlice(
const QVector<MTPGroupCallParticipant> &list,
ApplySliceSource sliceSource) {
@@ -565,7 +659,7 @@ void GroupCall::applyActiveUpdate(
void GroupCall::checkFinishSpeakingByActive() {
const auto now = crl::now();
auto nearest = 0;
auto nearest = crl::time(0);
auto stop = std::vector<not_null<PeerData*>>();
for (auto i = begin(_speakingByActiveFinishes)
; i != end(_speakingByActiveFinishes);) {

View File

@@ -42,6 +42,7 @@ public:
~GroupCall();
[[nodiscard]] uint64 id() const;
[[nodiscard]] bool loaded() const;
[[nodiscard]] not_null<PeerData*> peer() const;
[[nodiscard]] MTPInputGroupCall input() const;
[[nodiscard]] QString title() const {
@@ -112,9 +113,14 @@ private:
UnknownLoaded,
UpdateReceived,
};
enum class QueuedType : uint8 {
VersionedParticipant,
Participant,
Call,
};
[[nodiscard]] ApiWrap &api() const;
void discard();
void discard(const MTPDgroupCallDiscarded &data);
[[nodiscard]] bool inCall() const;
void applyParticipantsSlice(
const QVector<MTPGroupCallParticipant> &list,
@@ -123,10 +129,16 @@ private:
void changePeerEmptyCallFlag();
void checkFinishSpeakingByActive();
void applyCallFields(const MTPDgroupCall &data);
void applyUpdate(const MTPUpdate &update);
void applyEnqueuedUpdate(const MTPUpdate &update);
void setServerParticipantsCount(int count);
void computeParticipantsCount();
void processQueuedUpdates();
void processFullCallUsersChats(const MTPphone_GroupCall &call);
void processFullCallFields(const MTPphone_GroupCall &call);
[[nodiscard]] bool requestParticipantsAfterReload(
const MTPphone_GroupCall &call) const;
void processSavedFullCall();
void finishParticipantsSliceRequest();
const uint64 _id = 0;
const uint64 _accessHash = 0;
@@ -137,8 +149,11 @@ private:
mtpRequestId _reloadRequestId = 0;
rpl::variable<QString> _title;
base::flat_map<std::pair<int,bool>, MTPUpdate> _queuedUpdates;
base::flat_multi_map<
std::pair<int, QueuedType>,
MTPUpdate> _queuedUpdates;
base::Timer _reloadByQueuedUpdatesTimer;
std::optional<MTPphone_GroupCall> _savedFull;
std::vector<Participant> _participants;
base::flat_map<uint32, not_null<PeerData*>> _participantPeerBySsrc;
@@ -160,6 +175,7 @@ private:
bool _canChangeJoinMuted = true;
bool _allParticipantsLoaded = false;
bool _joinedToTop = false;
bool _applyingQueuedUpdates = false;
};

View File

@@ -1172,4 +1172,16 @@ std::optional<int> ResolvePinnedCount(
: std::nullopt;
}
ChatRestrictions ChatBannedRightsFlags(const MTPChatBannedRights &rights) {
return rights.match([](const MTPDchatBannedRights &data) {
return data.vflags().v;
});
}
TimeId ChatBannedRightsUntilDate(const MTPChatBannedRights &rights) {
return rights.match([](const MTPDchatBannedRights &data) {
return data.vuntil_date().v;
});
}
} // namespace Data

View File

@@ -95,6 +95,11 @@ struct UnavailableReason {
}
};
[[nodiscard]] ChatRestrictions ChatBannedRightsFlags(
const MTPChatBannedRights &rights);
[[nodiscard]] TimeId ChatBannedRightsUntilDate(
const MTPChatBannedRights &rights);
} // namespace Data
class PeerClickHandler : public ClickHandler {

View File

@@ -432,8 +432,13 @@ void InnerWidget::requestAdmins() {
auto filtered = (
list
) | ranges::views::transform([&](const MTPChannelParticipant &p) {
const auto userId = p.match([](const auto &data) {
return data.vuser_id().v;
const auto participantId = p.match([](
const MTPDchannelParticipantBanned &data) {
return peerFromMTP(data.vpeer());
}, [](const MTPDchannelParticipantLeft &data) {
return peerFromMTP(data.vpeer());
}, [](const auto &data) {
return peerFromUser(data.vuser_id());
});
const auto canEdit = p.match([](
const MTPDchannelParticipantAdmin &data) {
@@ -441,10 +446,13 @@ void InnerWidget::requestAdmins() {
}, [](const auto &) {
return false;
});
return std::make_pair(userId, canEdit);
return std::make_pair(participantId, canEdit);
}) | ranges::views::transform([&](auto &&pair) {
return std::make_pair(
session().data().userLoaded(pair.first),
(peerIsUser(pair.first)
? session().data().userLoaded(
peerToUser(pair.first))
: nullptr),
pair.second);
}) | ranges::views::filter([&](auto &&pair) {
return (pair.first != nullptr);
@@ -1304,11 +1312,11 @@ void InnerWidget::suggestRestrictUser(not_null<UserData*> user) {
Ui::LayerOption::KeepOther);
};
if (base::contains(_admins, user)) {
editRestrictions(true, MTP_chatBannedRights(MTP_flags(0), MTP_int(0)));
editRestrictions(true, ChannelData::EmptyRestrictedRights(user));
} else {
_api.request(MTPchannels_GetParticipant(
_channel->inputChannel,
user->inputUser
user->input
)).done([=](const MTPchannels_ChannelParticipant &result) {
Expects(result.type() == mtpc_channels_channelParticipant);
@@ -1321,15 +1329,11 @@ void InnerWidget::suggestRestrictUser(not_null<UserData*> user) {
} else {
auto hasAdminRights = (type == mtpc_channelParticipantAdmin)
|| (type == mtpc_channelParticipantCreator);
auto bannedRights = MTP_chatBannedRights(
MTP_flags(0),
MTP_int(0));
auto bannedRights = ChannelData::EmptyRestrictedRights(user);
editRestrictions(hasAdminRights, bannedRights);
}
}).fail([=](const MTP::Error &error) {
auto bannedRights = MTP_chatBannedRights(
MTP_flags(0),
MTP_int(0));
auto bannedRights = ChannelData::EmptyRestrictedRights(user);
editRestrictions(false, bannedRights);
}).send();
}
@@ -1352,8 +1356,7 @@ void InnerWidget::restrictUser(
}
void InnerWidget::restrictUserDone(not_null<UserData*> user, const MTPChatBannedRights &rights) {
Expects(rights.type() == mtpc_chatBannedRights);
if (rights.c_chatBannedRights().vflags().v) {
if (Data::ChatBannedRightsFlags(rights)) {
_admins.erase(std::remove(_admins.begin(), _admins.end(), user), _admins.end());
_adminsCanEdit.erase(std::remove(_adminsCanEdit.begin(), _adminsCanEdit.end(), user), _adminsCanEdit.end());
}

View File

@@ -204,14 +204,11 @@ TextWithEntities GenerateAdminChangeText(
QString GenerateBannedChangeText(
const MTPChatBannedRights *newRights,
const MTPChatBannedRights *prevRights) {
Expects(!newRights || newRights->type() == mtpc_chatBannedRights);
Expects(!prevRights || prevRights->type() == mtpc_chatBannedRights);
using Flag = MTPDchatBannedRights::Flag;
using Flags = MTPDchatBannedRights::Flags;
auto newFlags = newRights ? newRights->c_chatBannedRights().vflags().v : Flags(0);
auto prevFlags = prevRights ? prevRights->c_chatBannedRights().vflags().v : Flags(0);
auto newFlags = newRights ? Data::ChatBannedRightsFlags(*newRights) : Flags(0);
auto prevFlags = prevRights ? Data::ChatBannedRightsFlags(*prevRights) : Flags(0);
static auto phraseMap = std::map<Flags, tr::phrase<>>{
{ Flag::f_view_messages, tr::lng_admin_log_banned_view_messages },
{ Flag::f_send_messages, tr::lng_admin_log_banned_send_messages },
@@ -230,19 +227,21 @@ QString GenerateBannedChangeText(
}
TextWithEntities GenerateBannedChangeText(
PeerId participantId,
const TextWithEntities &user,
const MTPChatBannedRights *newRights,
const MTPChatBannedRights *prevRights) {
Expects(!newRights || newRights->type() == mtpc_chatBannedRights);
using Flag = MTPDchatBannedRights::Flag;
using Flags = MTPDchatBannedRights::Flags;
auto newFlags = newRights ? newRights->c_chatBannedRights().vflags().v : Flags(0);
auto newUntil = newRights ? newRights->c_chatBannedRights().vuntil_date().v : TimeId(0);
auto newFlags = newRights ? Data::ChatBannedRightsFlags(*newRights) : Flags(0);
auto newUntil = newRights ? Data::ChatBannedRightsUntilDate(*newRights) : TimeId(0);
auto prevFlags = prevRights ? Data::ChatBannedRightsFlags(*prevRights) : Flags(0);
auto indefinitely = ChannelData::IsRestrictedForever(newUntil);
if (newFlags & Flag::f_view_messages) {
return tr::lng_admin_log_banned(tr::now, lt_user, user, Ui::Text::WithEntities);
} else if (newFlags == 0 && (prevFlags & Flag::f_view_messages) && !peerIsUser(participantId)) {
return tr::lng_admin_log_unbanned(tr::now, lt_user, user, Ui::Text::WithEntities);
}
auto untilText = indefinitely
? tr::lng_admin_log_restricted_forever(tr::now)
@@ -344,21 +343,23 @@ TextWithEntities GenerateInviteLinkChangeText(
return result;
};
auto GenerateUserString(
auto GenerateParticipantString(
not_null<Main::Session*> session,
MTPint userId) {
PeerId participantId) {
// User name in "User name (@username)" format with entities.
auto user = session->data().user(userId.v);
auto name = TextWithEntities { user->name };
auto entityData = QString::number(user->id)
+ '.'
+ QString::number(user->accessHash());
name.entities.push_back({
EntityType::MentionName,
0,
name.text.size(),
entityData });
auto username = user->userName();
auto peer = session->data().peer(participantId);
auto name = TextWithEntities { peer->name };
if (const auto user = peer->asUser()) {
auto entityData = QString::number(user->id)
+ '.'
+ QString::number(user->accessHash());
name.entities.push_back({
EntityType::MentionName,
0,
name.text.size(),
entityData });
}
auto username = peer->userName();
if (username.isEmpty()) {
return name;
}
@@ -381,32 +382,10 @@ auto GenerateParticipantChangeTextInner(
const MTPChannelParticipant &participant,
const MTPChannelParticipant *oldParticipant) {
const auto oldType = oldParticipant ? oldParticipant->type() : 0;
return participant.match([&](const MTPDchannelParticipantCreator &data) {
// No valid string here :(
return tr::lng_admin_log_transferred(
tr::now,
lt_user,
GenerateUserString(&channel->session(), data.vuser_id()),
Ui::Text::WithEntities);
}, [&](const MTPDchannelParticipantAdmin &data) {
auto user = GenerateUserString(&channel->session(), data.vuser_id());
return GenerateAdminChangeText(
channel,
user,
&data.vadmin_rights(),
(oldType == mtpc_channelParticipantAdmin)
? &oldParticipant->c_channelParticipantAdmin().vadmin_rights()
: nullptr);
}, [&](const MTPDchannelParticipantBanned &data) {
auto user = GenerateUserString(&channel->session(), data.vuser_id());
return GenerateBannedChangeText(
user,
&data.vbanned_rights(),
(oldType == mtpc_channelParticipantBanned)
? &oldParticipant->c_channelParticipantBanned().vbanned_rights()
: nullptr);
}, [&](const auto &data) {
auto user = GenerateUserString(&channel->session(), data.vuser_id());
const auto generateOther = [&](PeerId participantId) {
auto user = GenerateParticipantString(
&channel->session(),
participantId);
if (oldType == mtpc_channelParticipantAdmin) {
return GenerateAdminChangeText(
channel,
@@ -415,11 +394,49 @@ auto GenerateParticipantChangeTextInner(
&oldParticipant->c_channelParticipantAdmin().vadmin_rights());
} else if (oldType == mtpc_channelParticipantBanned) {
return GenerateBannedChangeText(
participantId,
user,
nullptr,
&oldParticipant->c_channelParticipantBanned().vbanned_rights());
}
return tr::lng_admin_log_invited(tr::now, lt_user, user, Ui::Text::WithEntities);
};
return participant.match([&](const MTPDchannelParticipantCreator &data) {
// No valid string here :(
return tr::lng_admin_log_transferred(
tr::now,
lt_user,
GenerateParticipantString(
&channel->session(),
peerFromUser(data.vuser_id())),
Ui::Text::WithEntities);
}, [&](const MTPDchannelParticipantAdmin &data) {
const auto user = GenerateParticipantString(
&channel->session(),
peerFromUser(data.vuser_id()));
return GenerateAdminChangeText(
channel,
user,
&data.vadmin_rights(),
(oldType == mtpc_channelParticipantAdmin
? &oldParticipant->c_channelParticipantAdmin().vadmin_rights()
: nullptr));
}, [&](const MTPDchannelParticipantBanned &data) {
const auto participantId = peerFromMTP(data.vpeer());
const auto user = GenerateParticipantString(
&channel->session(),
participantId);
return GenerateBannedChangeText(
participantId,
user,
&data.vbanned_rights(),
(oldType == mtpc_channelParticipantBanned
? &oldParticipant->c_channelParticipantBanned().vbanned_rights()
: nullptr));
}, [&](const MTPDchannelParticipantLeft &data) {
return generateOther(peerFromMTP(data.vpeer()));
}, [&](const auto &data) {
return generateOther(peerFromUser(data.vuser_id()));
});
}

View File

@@ -701,9 +701,11 @@ bool HistoryItem::suggestReport() const {
}
bool HistoryItem::suggestBanReport() const {
auto channel = history()->peer->asChannel();
auto fromUser = from()->asUser();
if (!channel || !fromUser || !channel->canRestrictUser(fromUser)) {
const auto channel = history()->peer->asChannel();
const auto fromUser = from()->asUser();
if (!channel
|| !fromUser
|| !channel->canRestrictParticipant(fromUser)) {
return false;
}
return !isPost() && !out();

View File

@@ -535,7 +535,10 @@ void ValidateSlice(
).arg(from
).arg(index
).arg(till) + strings.join(","));
Unexpected("Bad slice in GroupThumbs.");
if (Logs::DebugEnabled()) {
Unexpected("Bad slice in GroupThumbs.");
}
break;
} else {
keys.emplace(key);
}
@@ -555,9 +558,7 @@ void GroupThumbs::fillItems(
const auto current = (index - from);
const auto old = base::take(_items);
if (Logs::DebugEnabled()) {
ValidateSlice(slice, _context, from, index, till);
}
//ValidateSlice(slice, _context, from, index, till);
markCacheStale();
_items.reserve(till - from);
@@ -586,17 +587,21 @@ void GroupThumbs::animateAliveItems(int current) {
}
void GroupThumbs::fillDyingItems(const std::vector<not_null<Thumb*>> &old) {
Expects(_cache.size() >= _items.size());
//Expects(_cache.size() >= _items.size());
_dying.reserve(_cache.size() - _items.size());
if (_cache.size() >= _items.size()) {
_dying.reserve(_cache.size() - _items.size());
}
animatePreviouslyAlive(old);
markRestAsDying();
}
void GroupThumbs::markRestAsDying() {
Expects(_cache.size() >= _items.size());
//Expects(_cache.size() >= _items.size());
_dying.reserve(_cache.size() - _items.size());
if (_cache.size() >= _items.size()) {
_dying.reserve(_cache.size() - _items.size());
}
for (const auto &cacheItem : _cache) {
const auto &thumb = cacheItem.second;
const auto state = thumb->state();

View File

@@ -0,0 +1,461 @@
/*
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 "platform/linux/linux_mpris_support.h"
#include "base/platform/base_platform_info.h"
#include "base/platform/linux/base_linux_glibmm_helper.h"
#include "media/audio/media_audio.h"
#include "media/player/media_player_instance.h"
#include "data/data_document.h"
#include "core/sandbox.h"
#include "core/application.h"
#include "core/core_settings.h"
#include "mainwindow.h"
#include "app.h"
#include <QtGui/QGuiApplication>
#include <glibmm.h>
#include <giomm.h>
namespace Platform {
namespace internal {
namespace {
constexpr auto kService = "org.mpris.MediaPlayer2.tdesktop"_cs;
constexpr auto kObjectPath = "/org/mpris/MediaPlayer2"_cs;
constexpr auto kFakeTrackPath = "/org/telegram/desktop/track/0"_cs;
constexpr auto kInterface = "org.mpris.MediaPlayer2"_cs;
constexpr auto kPlayerInterface = "org.mpris.MediaPlayer2.Player"_cs;
constexpr auto kPropertiesInterface = "org.freedesktop.DBus.Properties"_cs;
constexpr auto kSongType = AudioMsgId::Type::Song;
constexpr auto kIntrospectionXML = R"INTROSPECTION(<node>
<interface name='org.mpris.MediaPlayer2'>
<method name='Raise'/>
<method name='Quit'/>
<property name='CanQuit' type='b' access='read'/>
<property name='CanRaise' type='b' access='read'/>
<property name='HasTrackList' type='b' access='read'/>
<property name='Identity' type='s' access='read'/>
<property name='DesktopEntry' type='s' access='read'/>
<property name='SupportedUriSchemes' type='as' access='read'/>
<property name='SupportedMimeTypes' type='as' access='read'/>
<property name='Fullscreen' type='b' access='readwrite'/>
<property name='CanSetFullscreen' type='b' access='read'/>
</interface>
</node>)INTROSPECTION"_cs;
constexpr auto kPlayerIntrospectionXML = R"INTROSPECTION(<node>
<interface name='org.mpris.MediaPlayer2.Player'>
<method name='Next'/>
<method name='Previous'/>
<method name='Pause'/>
<method name='PlayPause'/>
<method name='Stop'/>
<method name='Play'/>
<method name='Seek'>
<arg direction='in' name='Offset' type='x'/>
</method>
<method name='SetPosition'>
<arg direction='in' name='TrackId' type='o'/>
<arg direction='in' name='Position' type='x'/>
</method>
<method name='OpenUri'>
<arg direction='in' name='Uri' type='s'/>
</method>
<signal name='Seeked'>
<arg name='Position' type='x'/>
</signal>
<property name='PlaybackStatus' type='s' access='read'/>
<property name='Rate' type='d' access='readwrite'/>
<property name='Metadata' type='a{sv}' access='read'>
<annotation name="org.qtproject.QtDBus.QtTypeName" value="QVariantMap"/>
</property>
<property name='Volume' type='d' access='readwrite'/>
<property name='Position' type='x' access='read'/>
<property name='MinimumRate' type='d' access='read'/>
<property name='MaximumRate' type='d' access='read'/>
<property name='CanGoNext' type='b' access='read'/>
<property name='CanGoPrevious' type='b' access='read'/>
<property name='CanPlay' type='b' access='read'/>
<property name='CanPause' type='b' access='read'/>
<property name='CanSeek' type='b' access='read'/>
<property name='CanControl' type='b' access='read'/>
</interface>
</node>)INTROSPECTION"_cs;
auto CreateMetadata(const Media::Player::TrackState &state) {
std::map<Glib::ustring, Glib::VariantBase> result;
if (!Media::Player::IsStoppedOrStopping(state.state)) {
result["mpris:trackid"] = Glib::wrap(g_variant_new_object_path(
kFakeTrackPath.utf8().constData()));
result["mpris:length"] = Glib::Variant<long>::create(
state.length * 1000);
const auto audioData = state.id.audio();
if (audioData) {
result["xesam:title"] = Glib::Variant<
Glib::ustring
>::create(audioData->filename().toStdString());
if (audioData->isSong()) {
const auto songData = audioData->song();
if (!songData->performer.isEmpty()) {
result["xesam:artist"] = Glib::Variant<
std::vector<Glib::ustring>
>::create({ songData->performer.toStdString() });
}
if (!songData->performer.isEmpty()) {
result["xesam:title"] = Glib::Variant<
Glib::ustring
>::create(songData->title.toStdString());
}
}
}
}
return result;
}
auto PlaybackStatus(Media::Player::State state) {
return (state == Media::Player::State::Playing)
? "Playing"
: Media::Player::IsPausedOrPausing(state)
? "Paused"
: "Stopped";
}
void HandleMethodCall(
const Glib::RefPtr<Gio::DBus::Connection> &connection,
const Glib::ustring &sender,
const Glib::ustring &object_path,
const Glib::ustring &interface_name,
const Glib::ustring &method_name,
const Glib::VariantContainerBase &parameters,
const Glib::RefPtr<Gio::DBus::MethodInvocation> &invocation) {
Core::Sandbox::Instance().customEnterFromEventLoop([&] {
try {
auto parametersCopy = parameters;
if (method_name == "Quit") {
App::quit();
} else if (method_name == "Raise") {
App::wnd()->showFromTray();
} else if (method_name == "Next") {
Media::Player::instance()->next();
} else if (method_name == "Pause") {
Media::Player::instance()->pause();
} else if (method_name == "Play") {
Media::Player::instance()->play();
} else if (method_name == "PlayPause") {
Media::Player::instance()->playPause();
} else if (method_name == "Previous") {
Media::Player::instance()->previous();
} else if (method_name == "Seek") {
const auto offset = base::Platform::GlibVariantCast<long>(
parametersCopy.get_child(0));
const auto state = Media::Player::instance()->getState(
kSongType);
Media::Player::instance()->finishSeeking(
kSongType,
float64(state.position * 1000 + offset)
/ (state.length * 1000));
} else if (method_name == "SetPosition") {
const auto position = base::Platform::GlibVariantCast<long>(
parametersCopy.get_child(1));
const auto state = Media::Player::instance()->getState(
kSongType);
Media::Player::instance()->finishSeeking(
kSongType,
float64(position) / (state.length * 1000));
} else if (method_name == "Stop") {
Media::Player::instance()->stop();
} else {
return;
}
invocation->return_value({});
} catch (...) {
}
});
}
void HandleGetProperty(
Glib::VariantBase &property,
const Glib::RefPtr<Gio::DBus::Connection> &connection,
const Glib::ustring &sender,
const Glib::ustring &object_path,
const Glib::ustring &interface_name,
const Glib::ustring &property_name) {
Core::Sandbox::Instance().customEnterFromEventLoop([&] {
if (property_name == "CanQuit") {
property = Glib::Variant<bool>::create(true);
} else if (property_name == "CanRaise") {
property = Glib::Variant<bool>::create(!IsWayland());
} else if (property_name == "CanSetFullscreen") {
property = Glib::Variant<bool>::create(false);
} else if (property_name == "DesktopEntry") {
property = Glib::Variant<Glib::ustring>::create(
QGuiApplication::desktopFileName().chopped(8).toStdString());
} else if (property_name == "Fullscreen") {
property = Glib::Variant<bool>::create(false);
} else if (property_name == "HasTrackList") {
property = Glib::Variant<bool>::create(false);
} else if (property_name == "Identity") {
property = Glib::Variant<Glib::ustring>::create(
std::string(AppName));
} else if (property_name == "SupportedMimeTypes") {
property = Glib::Variant<std::vector<Glib::ustring>>::create({});
} else if (property_name == "SupportedUriSchemes") {
property = Glib::Variant<std::vector<Glib::ustring>>::create({});
} else if (property_name == "CanControl") {
property = Glib::Variant<bool>::create(true);
} else if (property_name == "CanGoNext") {
property = Glib::Variant<bool>::create(true);
} else if (property_name == "CanGoPrevious") {
property = Glib::Variant<bool>::create(true);
} else if (property_name == "CanPause") {
property = Glib::Variant<bool>::create(true);
} else if (property_name == "CanPlay") {
property = Glib::Variant<bool>::create(true);
} else if (property_name == "CanSeek") {
property = Glib::Variant<bool>::create(true);
} else if (property_name == "MaximumRate") {
property = Glib::Variant<float64>::create(1.0);
} else if (property_name == "Metadata") {
const auto state = Media::Player::instance()->getState(
kSongType);
property = base::Platform::MakeGlibVariant(
CreateMetadata(state));
} else if (property_name == "MinimumRate") {
property = Glib::Variant<float64>::create(1.0);
} else if (property_name == "PlaybackStatus") {
const auto state = Media::Player::instance()->getState(
kSongType);
property = Glib::Variant<Glib::ustring>::create(
PlaybackStatus(state.state));
} else if (property_name == "Position") {
const auto state = Media::Player::instance()->getState(
kSongType);
property = Glib::Variant<long>::create(state.position * 1000);
} else if (property_name == "Rate") {
property = Glib::Variant<float64>::create(1.0);
} else if (property_name == "Volume") {
property = Glib::Variant<float64>::create(
Core::App().settings().songVolume());
}
});
}
bool HandleSetProperty(
const Glib::RefPtr<Gio::DBus::Connection> &connection,
const Glib::ustring &sender,
const Glib::ustring &object_path,
const Glib::ustring &interface_name,
const Glib::ustring &property_name,
const Glib::VariantBase &value) {
try {
if (property_name == "Fullscreen") {
} else if (property_name == "Rate") {
} else if (property_name == "Volume") {
Core::Sandbox::Instance().customEnterFromEventLoop([&] {
Core::App().settings().setSongVolume(
base::Platform::GlibVariantCast<float64>(value));
});
} else {
return false;
}
return true;
} catch (...) {
}
return false;
}
const Gio::DBus::InterfaceVTable InterfaceVTable(
sigc::ptr_fun(&HandleMethodCall),
sigc::ptr_fun(&HandleGetProperty),
sigc::ptr_fun(&HandleSetProperty));
void PlayerPropertyChanged(
const Glib::ustring &name,
const Glib::VariantBase &value) {
try {
const auto connection = Gio::DBus::Connection::get_sync(
Gio::DBus::BusType::BUS_TYPE_SESSION);
connection->emit_signal(
std::string(kObjectPath),
std::string(kPropertiesInterface),
"PropertiesChanged",
{},
base::Platform::MakeGlibVariant(std::tuple{
Glib::ustring(std::string(kPlayerInterface)),
std::map<Glib::ustring, Glib::VariantBase>{
{ name, value },
},
std::vector<Glib::ustring>{},
}));
} catch (...) {
}
}
void Seeked(long position) {
try {
const auto connection = Gio::DBus::Connection::get_sync(
Gio::DBus::BusType::BUS_TYPE_SESSION);
connection->emit_signal(
std::string(kObjectPath),
std::string(kPlayerInterface),
"Seeked",
{},
base::Platform::MakeGlibVariant(std::tuple{
position,
}));
} catch (...) {
}
}
} // namespace
class MPRISSupport::Private {
public:
void updateTrackState(const Media::Player::TrackState &state);
Glib::RefPtr<Gio::DBus::Connection> dbusConnection;
Glib::RefPtr<Gio::DBus::NodeInfo> introspectionData;
Glib::RefPtr<Gio::DBus::NodeInfo> playerIntrospectionData;
uint ownId = 0;
uint registerId = 0;
uint playerRegisterId = 0;
std::map<Glib::ustring, Glib::VariantBase> metadata;
Glib::ustring playbackStatus;
long position = 0;
rpl::lifetime lifetime;
};
void MPRISSupport::Private::updateTrackState(
const Media::Player::TrackState &state) {
if (state.id.type() != kSongType) {
return;
}
const auto currentMetadata = CreateMetadata(state);
const auto currentPosition = state.position * 1000;
const auto currentPlaybackStatus = PlaybackStatus(state.state);
if (!ranges::equal(currentMetadata, metadata, [&](
const auto &item1,
const auto &item2) {
return item1.first == item2.first
&& item1.second.equal(item2.second);
})) {
metadata = currentMetadata;
PlayerPropertyChanged(
"Metadata",
Glib::Variant<
std::map<Glib::ustring, Glib::VariantBase>
>::create(metadata));
}
if (currentPlaybackStatus != playbackStatus) {
playbackStatus = currentPlaybackStatus;
PlayerPropertyChanged(
"PlaybackStatus",
Glib::Variant<Glib::ustring>::create(playbackStatus));
}
if (currentPosition != position) {
const auto positionDifference = position - currentPosition;
if (positionDifference > 1000000 || positionDifference < -1000000) {
Seeked(currentPosition);
}
position = currentPosition;
}
}
MPRISSupport::MPRISSupport()
: _private(std::make_unique<Private>()) {
try {
_private->introspectionData = Gio::DBus::NodeInfo::create_for_xml(
std::string(kIntrospectionXML));
_private->playerIntrospectionData = Gio::DBus::NodeInfo::create_for_xml(
std::string(kPlayerIntrospectionXML));
_private->ownId = Gio::DBus::own_name(
Gio::DBus::BusType::BUS_TYPE_SESSION,
std::string(kService));
_private->dbusConnection = Gio::DBus::Connection::get_sync(
Gio::DBus::BusType::BUS_TYPE_SESSION);
_private->registerId = _private->dbusConnection->register_object(
std::string(kObjectPath),
_private->introspectionData->lookup_interface(),
InterfaceVTable);
_private->playerRegisterId = _private->dbusConnection->register_object(
std::string(kObjectPath),
_private->playerIntrospectionData->lookup_interface(),
InterfaceVTable);
_private->updateTrackState(
Media::Player::instance()->getState(kSongType));
Media::Player::instance()->updatedNotifier(
) | rpl::start_with_next([=](
const Media::Player::TrackState &state) {
_private->updateTrackState(state);
}, _private->lifetime);
Core::App().settings().songVolumeChanges(
) | rpl::start_with_next([=](float64 volume) {
PlayerPropertyChanged(
"Volume",
Glib::Variant<float64>::create(volume));
}, _private->lifetime);
} catch (...) {
}
}
MPRISSupport::~MPRISSupport() {
if (_private->dbusConnection) {
if (_private->playerRegisterId) {
_private->dbusConnection->unregister_object(
_private->playerRegisterId);
}
if (_private->registerId) {
_private->dbusConnection->unregister_object(
_private->registerId);
}
}
if (_private->ownId) {
Gio::DBus::unown_name(_private->ownId);
}
}
} // namespace internal
} // namespace Platform

View File

@@ -0,0 +1,24 @@
/*
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
*/
#pragma once
namespace Platform {
namespace internal {
class MPRISSupport {
public:
MPRISSupport();
~MPRISSupport();
private:
class Private;
const std::unique_ptr<Private> _private;
};
} // namespace internal
} // namespace Platform

View File

@@ -27,6 +27,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#ifndef DESKTOP_APP_DISABLE_DBUS_INTEGRATION
#include "base/platform/linux/base_linux_dbus_utilities.h"
#include "platform/linux/linux_notification_service_watcher.h"
#include "platform/linux/linux_mpris_support.h"
#include "platform/linux/linux_gsd_media_keys.h"
#endif // !DESKTOP_APP_DISABLE_DBUS_INTEGRATION
@@ -71,6 +72,10 @@ constexpr auto kXDGDesktopPortalService = "org.freedesktop.portal.Desktop"_cs;
constexpr auto kXDGDesktopPortalObjectPath = "/org/freedesktop/portal/desktop"_cs;
constexpr auto kIBusPortalService = "org.freedesktop.portal.IBus"_cs;
constexpr auto kSnapcraftSettingsService = "io.snapcraft.Settings"_cs;
constexpr auto kSnapcraftSettingsObjectPath = "/io/snapcraft/Settings"_cs;
constexpr auto kSnapcraftSettingsInterface = kSnapcraftSettingsService;
#ifndef DESKTOP_APP_DISABLE_DBUS_INTEGRATION
std::unique_ptr<internal::NotificationServiceWatcher> NSWInstance;
@@ -196,6 +201,68 @@ PortalAutostart::PortalAutostart(bool start, bool silent) {
}
}
class SnapDefaultHandler : public QWindow {
public:
SnapDefaultHandler(const QString &protocol);
};
SnapDefaultHandler::SnapDefaultHandler(const QString &protocol) {
try {
const auto connection = Gio::DBus::Connection::get_sync(
Gio::DBus::BusType::BUS_TYPE_SESSION);
auto reply = connection->call_sync(
std::string(kSnapcraftSettingsObjectPath),
std::string(kSnapcraftSettingsInterface),
"GetSub",
base::Platform::MakeGlibVariant(std::tuple{
Glib::ustring("default-url-scheme-handler"),
Glib::ustring(protocol.toStdString()),
}),
std::string(kSnapcraftSettingsService));
const auto currentHandler = base::Platform::GlibVariantCast<
Glib::ustring>(reply.get_child(0));
const auto expectedHandler = qEnvironmentVariable("SNAP_NAME")
+ qsl(".desktop");
if (currentHandler == expectedHandler.toStdString()) {
return;
}
QEventLoop loop;
connection->call(
std::string(kSnapcraftSettingsObjectPath),
std::string(kSnapcraftSettingsInterface),
"SetSub",
base::Platform::MakeGlibVariant(std::tuple{
Glib::ustring("default-url-scheme-handler"),
Glib::ustring(protocol.toStdString()),
Glib::ustring(expectedHandler.toStdString()),
}),
[&](const Glib::RefPtr<Gio::AsyncResult> &result) {
try {
connection->call_finish(result);
} catch (const Glib::Error &e) {
LOG(("Snap Default Handler Error: %1").arg(
QString::fromStdString(e.what())));
}
loop.quit();
},
std::string(kSnapcraftSettingsService));
QGuiApplicationPrivate::showModalWindow(this);
loop.exec();
QGuiApplicationPrivate::hideModalWindow(this);
} catch (const Glib::Error &e) {
LOG(("Snap Default Handler Error: %1").arg(
QString::fromStdString(e.what())));
}
}
bool IsIBusPortalPresent() {
static const auto Result = [&] {
try {
@@ -347,12 +414,25 @@ void SetGtkScaleFactor() {
void SetWatchingMediaKeys(bool watching) {
#ifndef DESKTOP_APP_DISABLE_DBUS_INTEGRATION
static std::unique_ptr<internal::GSDMediaKeys> Instance;
static std::unique_ptr<internal::MPRISSupport> MPRISInstance;
static std::unique_ptr<internal::GSDMediaKeys> GSDInstance;
if (watching && !Instance) {
Instance = std::make_unique<internal::GSDMediaKeys>();
} else if (!watching && Instance) {
Instance = nullptr;
if (watching) {
if (!MPRISInstance) {
MPRISInstance = std::make_unique<internal::MPRISSupport>();
}
if (!GSDInstance) {
GSDInstance = std::make_unique<internal::GSDMediaKeys>();
}
} else {
if (MPRISInstance) {
MPRISInstance = nullptr;
}
if (GSDInstance) {
GSDInstance = nullptr;
}
}
#endif // !DESKTOP_APP_DISABLE_DBUS_INTEGRATION
}
@@ -593,6 +673,9 @@ void start() {
Glib::init();
Gio::init();
Glib::set_prgname(cExeName().toStdString());
Glib::set_application_name(std::string(AppName));
if (const auto integration = BaseGtkIntegration::Instance()) {
integration->prepareEnvironment();
} else {
@@ -671,6 +754,11 @@ void InstallLauncher(bool force) {
void RegisterCustomScheme(bool force) {
try {
if (InSnap()) {
SnapDefaultHandler(qsl("tg"));
return;
}
if (cExeName().isEmpty()) {
return;
}

View File

@@ -84,7 +84,7 @@ void GroupMembersWidget::removePeer(PeerData *selectedPeer) {
return it->second.rights;
}
}
return MTP_chatBannedRights(MTP_flags(0), MTP_int(0));
return ChannelData::EmptyRestrictedRights(user);
}();
const auto peer = this->peer();

View File

@@ -12,9 +12,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
namespace Ui {
struct MultilineToastArgs {
QWidget *parentOverride = nullptr;
TextWithEntities text;
crl::time duration = 0;
QWidget *parentOverride = nullptr;
};
void ShowMultilineToast(MultilineToastArgs &&args);

View File

@@ -1068,11 +1068,18 @@ void PeerMenuAddChannelMembers(
auto already = (
list
) | ranges::views::transform([](const MTPChannelParticipant &p) {
return p.match([](const auto &data) {
return data.vuser_id().v;
return p.match([](const MTPDchannelParticipantBanned &data) {
return peerFromMTP(data.vpeer());
}, [](const MTPDchannelParticipantLeft &data) {
return peerFromMTP(data.vpeer());
}, [](const auto &data) {
return peerFromUser(data.vuser_id());
});
}) | ranges::views::transform([&](UserId userId) {
return channel->owner().userLoaded(userId);
}) | ranges::views::transform([&](PeerId participantId) {
return peerIsUser(participantId)
? channel->owner().userLoaded(
peerToUser(participantId))
: nullptr;
}) | ranges::views::filter([](UserData *user) {
return (user != nullptr);
}) | ranges::to_vector;

View File

@@ -190,27 +190,37 @@ void SessionNavigation::showPeerByLinkResolved(
MTPchannels_GetFullChannel(peer->asChannel()->inputChannel)
).done([=](const MTPmessages_ChatFull &result) {
_session->api().processFullPeer(peer, result);
if (const auto call = peer->groupCall()) {
const auto id = call->id();
_resolveRequestId = _session->api().request(
MTPphone_GetGroupCall(call->input())
).done([=](const MTPphone_GroupCall &result) {
if (const auto now = peer->groupCall()
; now && now->id() == id) {
now->processFullCall(result);
parentController()->startOrJoinGroupCall(
peer,
hash,
SessionController::GroupCallJoinConfirm::Always);
} else {
bad();
}
}).fail([=](const MTP::Error &error) {
bad();
}).send();
} else {
const auto call = peer->groupCall();
if (!call) {
bad();
return;
}
const auto join = [=] {
parentController()->startOrJoinGroupCall(
peer,
hash,
SessionController::GroupCallJoinConfirm::Always);
};
if (call->loaded()) {
join();
return;
}
const auto id = call->id();
_resolveRequestId = _session->api().request(
MTPphone_GetGroupCall(call->input())
).done([=](const MTPphone_GroupCall &result) {
if (const auto now = peer->groupCall()
; now && now->id() == id) {
if (!now->loaded()) {
now->processFullCall(result);
}
join();
} else {
bad();
}
}).fail([=](const MTP::Error &error) {
bad();
}).send();
}).send();
return;
}

View File

@@ -138,10 +138,17 @@ if [ "$BuildTarget" == "linux" ] || [ "$BuildTarget" == "linux32" ]; then
echo "Copying from docker result folder."
cp "$ReleasePath/root/$BinaryName" "$ReleasePath/$BinaryName"
cp "$ReleasePath/root/$BinaryName.sym" "$ReleasePath/$BinaryName.sym"
cp "$ReleasePath/root/Updater" "$ReleasePath/Updater"
cp "$ReleasePath/root/Packer" "$ReleasePath/Packer"
echo "Dumping debug symbols.."
"$ReleasePath/dump_syms" "$ReleasePath/$BinaryName" > "$ReleasePath/$BinaryName.sym"
echo "Done!"
echo "Stripping the executable.."
strip -s "$ReleasePath/$BinaryName"
echo "Done!"
echo "Preparing version $AppVersionStrFull, executing Packer.."
cd "$ReleasePath"
"./Packer" -path "$BinaryName" -path Updater -version $VersionForPacker $AlphaBetaParam

View File

@@ -16,7 +16,7 @@ if [ ! -d "$FullScriptPath/../../../../DesktopPrivate" ]; then
fi
Run () {
scl enable devtoolset-8 -- "$@"
scl enable devtoolset-9 -- "$@"
}
HomePath="$FullScriptPath/../.."
@@ -86,17 +86,8 @@ if [ "$BadCount" != "0" ]; then
Error "Bad GCC usages found: $BadCount"
fi
echo "Dumping debug symbols.."
/dump_syms "$ReleasePath/$BinaryName" > "$ReleasePath/$BinaryName.sym"
echo "Done!"
echo "Stripping the executable.."
strip -s "$ReleasePath/$BinaryName"
echo "Done!"
rm -rf "$ReleasePath/root"
mkdir "$ReleasePath/root"
mv "$ReleasePath/$BinaryName" "$ReleasePath/root/"
mv "$ReleasePath/$BinaryName.sym" "$ReleasePath/root/"
mv "$ReleasePath/Updater" "$ReleasePath/root/"
mv "$ReleasePath/Packer" "$ReleasePath/root/"

View File

@@ -19,10 +19,10 @@ RUN yum -y install git cmake3 meson ninja-build autoconf automake libtool \
freetype-devel libX11-devel at-spi2-core-devel alsa-lib-devel \
pulseaudio-libs-devel mesa-libGL-devel mesa-libEGL-devel \
webkitgtk4-devel pkgconfig bison yasm file which xorg-x11-util-macros \
devtoolset-8-make devtoolset-8-gcc devtoolset-8-gcc-c++ \
devtoolset-8-binutils
devtoolset-9-make devtoolset-9-gcc devtoolset-9-gcc-c++ \
devtoolset-9-binutils
SHELL [ "scl", "enable", "devtoolset-8", "--", "bash", "-c" ]
SHELL [ "scl", "enable", "devtoolset-9", "--", "bash", "-c" ]
RUN ln -s cmake3 /usr/bin/cmake
ENV LibrariesPath /usr/src/Libraries
@@ -402,6 +402,7 @@ RUN make -j$(nproc)
RUN make DESTDIR="$LibrariesPath/ffmpeg-cache" install
FROM builder AS openal
ADD https://api.github.com/repos/telegramdesktop/openal-soft/git/refs/heads/fix_pulse_default openal-soft-version.json
RUN git clone -b fix_pulse_default --depth=1 $GIT/telegramdesktop/openal-soft.git
WORKDIR openal-soft
@@ -597,7 +598,7 @@ RUN git checkout bc8fb886
RUN git clone https://chromium.googlesource.com/linux-syscall-support.git src/third_party/lss
WORKDIR src/third_party/lss
RUN git checkout a91633d1
RUN git checkout 8048ece
WORKDIR ${LibrariesPath}
ENV BreakpadCache ${LibrariesPath}/breakpad-cache

View File

@@ -1,10 +1,10 @@
#!/bin/bash
cd Telegram
scl enable devtoolset-8 -- ./configure.sh "$@"
scl enable devtoolset-9 -- ./configure.sh "$@"
if [ -n "$DEBUG" ]; then
scl enable devtoolset-8 -- cmake3 --build ../out/Debug -j$(nproc)
scl enable devtoolset-9 -- cmake3 --build ../out/Debug -j$(nproc)
else
scl enable devtoolset-8 -- cmake3 --build ../out/Release -j$(nproc)
scl enable devtoolset-9 -- cmake3 --build ../out/Release -j$(nproc)
fi

View File

@@ -15,7 +15,7 @@ fi
Command="$1"
if [ "$Command" == "" ]; then
Command="scl enable devtoolset-8 -- bash"
Command="scl enable devtoolset-9 -- bash"
fi
docker run -it --rm --cpus=8 --memory=22g -v $HOME/Telegram/DesktopPrivate:/usr/src/DesktopPrivate -v $HOME/Telegram/tdesktop:/usr/src/tdesktop tdesktop:centos_env $Command

View File

@@ -1,7 +1,7 @@
AppVersion 2006004
AppVersionStrMajor 2.6
AppVersionStrSmall 2.6.4
AppVersionStr 2.6.4
BetaChannel 1
AppVersion 2007001
AppVersionStrMajor 2.7
AppVersionStrSmall 2.7.1
AppVersionStr 2.7.1
BetaChannel 0
AlphaVersion 0
AppVersionOriginal 2.6.4.beta
AppVersionOriginal 2.7.1

View File

@@ -27,6 +27,9 @@ if (NOT TGVOIP_FOUND)
init_target(lib_tgvoip_bundled cxx_std_14) # Can't use std::optional::value on macOS.
endif()
option(LIBTGVOIP_DISABLE_ALSA "Disable libtgvoip's ALSA backend (Linux only)." OFF)
option(LIBTGVOIP_DISABLE_PULSEAUDIO "Disable libtgvoip's PulseAudio backend (Linux only)." OFF)
set(tgvoip_loc ${third_party_loc}/libtgvoip)
nice_target_sources(lib_tgvoip_bundled ${tgvoip_loc}
@@ -180,15 +183,36 @@ if (NOT TGVOIP_FOUND)
)
if (LINUX)
find_package(PkgConfig REQUIRED)
find_package(ALSA REQUIRED)
pkg_check_modules(PULSE REQUIRED libpulse)
if (NOT LIBTGVOIP_DISABLE_ALSA)
find_package(ALSA REQUIRED)
target_include_directories(lib_tgvoip_bundled PRIVATE ${ALSA_INCLUDE_DIRS})
else()
remove_target_sources(lib_tgvoip_bundled ${tgvoip_loc}
os/linux/AudioInputALSA.cpp
os/linux/AudioInputALSA.h
os/linux/AudioOutputALSA.cpp
os/linux/AudioOutputALSA.h
)
target_include_directories(lib_tgvoip_bundled
PRIVATE
${ALSA_INCLUDE_DIRS}
${PULSE_INCLUDE_DIRS}
)
target_compile_definitions(lib_tgvoip_bundled PRIVATE WITHOUT_ALSA)
endif()
if (NOT LIBTGVOIP_DISABLE_PULSEAUDIO)
find_package(PkgConfig REQUIRED)
pkg_check_modules(PULSE REQUIRED libpulse)
target_include_directories(lib_tgvoip_bundled PRIVATE ${PULSE_INCLUDE_DIRS})
else()
remove_target_sources(lib_tgvoip_bundled ${tgvoip_loc}
os/linux/AudioOutputPulse.cpp
os/linux/AudioOutputPulse.h
os/linux/AudioInputPulse.cpp
os/linux/AudioInputPulse.h
os/linux/AudioPulse.cpp
os/linux/AudioPulse.h
)
target_compile_definitions(lib_tgvoip_bundled PRIVATE WITHOUT_PULSE)
endif()
target_link_libraries(lib_tgvoip_bundled
PRIVATE

View File

@@ -1,6 +1,42 @@
2.7.1 (20.03.21)
- Fix editing 'Manage Voice Chats' rights for channel admins.
- Fix verification check display in voice chat participants list.
- Allow removing and blocking channels from voice chats.
2.7 (19.03.21)
- Start limitless Voice Chats in Groups and Channels.
- Host discussions that can be listened to by millions of people simultaneously.
- Record voice chats to share or publish in Channels later.
- See that a chat is being recorded from the red dot next to its title.
- See user bio texts right from the list of participants.
- Raise your hand to show admins you want to speak.
- Create separate Voice Chat Invite Links for listeners or speakers.
- Change the title of your Voice Chat to give people an idea of the current topic.
- Join Voice Chats as one of your Channels to hide your personal account.
2.6.8 beta (18.03.21)
- Fix connecting and getting allowed to speak on voice chats.
- MPRIS support on Linux.
2.6.7 beta (18.03.21)
- Improve voice chat participants list updating.
2.6.6 beta (18.03.21)
- Fix joining popular voice chats.
2.6.5 beta (17.03.21)
- Improvements and fixes in new voice chat features.
2.6.4 beta (16.03.21)
- Fix freeze in voice chats.
- Make default interface scale 110% on macOS Retina screens.
2.6.3 beta (16.03.21)

View File

@@ -39,6 +39,8 @@ apps:
- unity7
- wayland
- x11
slots:
- tdesktop-mpris
plugs:
# Support for common GTK themes
@@ -57,6 +59,11 @@ plugs:
target: $SNAP/data-dir/sounds
default-provider: gtk-common-themes
slots:
tdesktop-mpris:
interface: mpris
name: tdesktop
layout:
/usr/share/alsa:
bind: $SNAP/usr/share/alsa