diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 115a19773..61cd43f59 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -2134,6 +2134,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_admin_log_deleted_message" = "{from} deleted message:"; "lng_admin_log_participant_joined" = "{from} joined the group"; "lng_admin_log_participant_joined_channel" = "{from} joined the channel"; +"lng_admin_log_participant_joined_by_link" = "{from} joined the group via {link}"; +"lng_admin_log_participant_joined_by_link_channel" = "{from} joined the channel via {link}"; +"lng_admin_log_revoke_invite_link" = "{from} revoked invite link {link}"; +"lng_admin_log_delete_invite_link" = "{from} deleted invite link {link}"; "lng_admin_log_participant_left" = "{from} left the group"; "lng_admin_log_participant_left_channel" = "{from} left the channel"; "lng_admin_log_stopped_poll" = "{from} stopped poll:"; @@ -2160,7 +2164,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_admin_log_unmuted_participant" = "{from} unmuted {user} in a voice chat"; "lng_admin_log_allowed_unmute_self" = "{from} allowed new voice chat members to speak"; "lng_admin_log_disallowed_unmute_self" = "{from} started muting new voice chat members"; +"lng_admin_log_participant_volume" = "{from} changed voice chat volume for {user} to {percent}"; "lng_admin_log_user_with_username" = "{name} ({mention})"; +"lng_admin_log_messages_ttl_set" = "{from} enabled messages auto-delete after {duration}"; +"lng_admin_log_messages_ttl_changed" = "{from} changed messages auto-delete period from {previous} to {duration}"; +"lng_admin_log_messages_ttl_removed" = "{from} disabled messages auto-deletion after {duration}"; +"lng_admin_log_edited_invite_link" = "edited invite link {link}"; +"lng_admin_log_invite_link_expire_date" = "Expire date: {previous} -> {limit}"; +"lng_admin_log_invite_link_usage_limit" = "Usage limit: {previous} -> {limit}"; "lng_admin_log_restricted_forever" = "indefinitely"; "lng_admin_log_restricted_until" = "until {date}"; "lng_admin_log_banned_view_messages" = "Read messages"; diff --git a/Telegram/SourceFiles/api/api_invite_links.cpp b/Telegram/SourceFiles/api/api_invite_links.cpp index 2b945fd55..5bc25514b 100644 --- a/Telegram/SourceFiles/api/api_invite_links.cpp +++ b/Telegram/SourceFiles/api/api_invite_links.cpp @@ -238,8 +238,8 @@ void InviteLinks::performEdit( using Flag = MTPmessages_EditExportedChatInvite::Flag; _api->request(MTPmessages_EditExportedChatInvite( MTP_flags((revoke ? Flag::f_revoked : Flag(0)) - | ((!revoke && expireDate) ? Flag::f_expire_date : Flag(0)) - | ((!revoke && usageLimit) ? Flag::f_usage_limit : Flag(0))), + | (!revoke ? Flag::f_expire_date : Flag(0)) + | (!revoke ? Flag::f_usage_limit : Flag(0))), peer->input, MTP_string(link), MTP_int(expireDate), diff --git a/Telegram/SourceFiles/core/local_url_handlers.cpp b/Telegram/SourceFiles/core/local_url_handlers.cpp index 9d47fa80d..5a3fa6701 100644 --- a/Telegram/SourceFiles/core/local_url_handlers.cpp +++ b/Telegram/SourceFiles/core/local_url_handlers.cpp @@ -26,6 +26,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "boxes/language_box.h" #include "passport/passport_form_controller.h" #include "window/window_session_controller.h" +#include "ui/toast/toast.h" #include "data/data_session.h" #include "data/data_document.h" #include "data/data_cloud_themes.h" @@ -40,6 +41,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "apiwrap.h" #include "app.h" +#include <QtGui/QGuiApplication> + namespace Core { namespace { @@ -433,6 +436,23 @@ bool OpenMediaTimestamp( return false; } +bool ShowInviteLink( + Window::SessionController *controller, + const Match &match, + const QVariant &context) { + if (!controller) { + return false; + } + const auto base64link = match->captured(1).toLatin1(); + const auto link = QString::fromUtf8(QByteArray::fromBase64(base64link)); + if (link.isEmpty()) { + return false; + } + QGuiApplication::clipboard()->setText(link); + Ui::Toast::Show(tr::lng_group_invite_copied(tr::now)); + return true; +} + } // namespace const std::vector<LocalUrlHandler> &LocalUrlHandlers() { @@ -507,6 +527,10 @@ const std::vector<LocalUrlHandler> &InternalUrlHandlers() { qsl("^media_timestamp/?\\?base=([a-zA-Z0-9\\.\\_\\-]+)&t=(\\d+)(&|$)"), OpenMediaTimestamp }, + { + qsl("^show_invite_link/?\\?link=([a-zA-Z0-9_\\+\\/\\=\\-]+)(&|$)"), + ShowInviteLink + }, }; return Result; } diff --git a/Telegram/SourceFiles/history/admin_log/history_admin_log_item.cpp b/Telegram/SourceFiles/history/admin_log/history_admin_log_item.cpp index 6c6f57517..02b820bdb 100644 --- a/Telegram/SourceFiles/history/admin_log/history_admin_log_item.cpp +++ b/Telegram/SourceFiles/history/admin_log/history_admin_log_item.cpp @@ -19,6 +19,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_session.h" #include "lang/lang_keys.h" #include "ui/text/text_utilities.h" +#include "ui/basic_click_handlers.h" #include "boxes/sticker_set_box.h" #include "base/unixtime.h" #include "core/application.h" @@ -261,6 +262,86 @@ TextWithEntities GenerateBannedChangeText( return result; } +QString ExtractInviteLink(const MTPExportedChatInvite &data) { + return data.match([&](const MTPDchatInviteExported &data) { + return qs(data.vlink()); + }); +} + +QString InternalInviteLinkUrl(const MTPExportedChatInvite &data) { + const auto base64 = ExtractInviteLink(data).toUtf8().toBase64(); + return "internal:show_invite_link/?link=" + QString::fromLatin1(base64); +} + +QString GenerateInviteLinkText(const MTPExportedChatInvite &data) { + return ExtractInviteLink(data).replace( + qstr("https://"), + QString() + ).replace( + qstr("t.me/+"), + QString() + ).replace( + qstr("t.me/joinchat/"), + QString() + ); +} + +QString GenerateInviteLinkLink(const MTPExportedChatInvite &data) { + const auto text = GenerateInviteLinkText(data); + return text.endsWith("...") + ? text + : textcmdLink(InternalInviteLinkUrl(data), text); +} + +TextWithEntities GenerateInviteLinkChangeText( + const MTPExportedChatInvite &newLink, + const MTPExportedChatInvite &prevLink) { + auto link = TextWithEntities{ GenerateInviteLinkText(newLink) }; + if (!link.text.endsWith("...")) { + link.entities.push_back({ + EntityType::CustomUrl, + 0, + link.text.size(), + InternalInviteLinkUrl(newLink) }); + } + auto result = tr::lng_admin_log_edited_invite_link(tr::now, lt_link, link, Ui::Text::WithEntities); + result.text.append('\n'); + + const auto expireDate = [](const MTPExportedChatInvite &link) { + return link.match([](const MTPDchatInviteExported &data) { + return data.vexpire_date().value_or_empty(); + }); + }; + const auto usageLimit = [](const MTPExportedChatInvite &link) { + return link.match([](const MTPDchatInviteExported &data) { + return data.vusage_limit().value_or_empty(); + }); + }; + const auto wrapDate = [](TimeId date) { + return date + ? langDateTime(base::unixtime::parse(date)) + : tr::lng_group_invite_expire_never(tr::now); + }; + const auto wrapUsage = [](int count) { + return count + ? QString::number(count) + : tr::lng_group_invite_usage_any(tr::now); + }; + const auto wasExpireDate = expireDate(prevLink); + const auto nowExpireDate = expireDate(newLink); + const auto wasUsageLimit = usageLimit(prevLink); + const auto nowUsageLimit = usageLimit(newLink); + if (wasExpireDate != nowExpireDate) { + result.text.append('\n').append(tr::lng_admin_log_invite_link_expire_date(tr::now, lt_previous, wrapDate(wasExpireDate), lt_limit, wrapDate(nowExpireDate))); + } + if (wasUsageLimit != nowUsageLimit) { + result.text.append('\n').append(tr::lng_admin_log_invite_link_usage_limit(tr::now, lt_previous, wrapUsage(wasUsageLimit), lt_limit, wrapUsage(nowUsageLimit))); + } + + result.entities.push_front(EntityInText(EntityType::Italic, 0, result.text.size())); + return result; +}; + auto GenerateUserString( not_null<Main::Session*> session, MTPint userId) { @@ -863,52 +944,49 @@ void GenerateItems( addSimpleServiceMessage(text); }; - auto createParticipantMute = [&](const MTPDchannelAdminLogEventActionParticipantMute &data) { - data.vparticipant().match([&](const MTPDgroupCallParticipant &data) { - const auto user = history->owner().user(data.vuser_id().v); - const auto userLink = user->createOpenLink(); - const auto userLinkText = textcmdLink(2, user->name); - auto text = tr::lng_admin_log_muted_participant( - tr::now, - lt_from, - fromLinkText, - lt_user, - userLinkText); - auto message = HistoryService::PreparedText{ text }; - message.links.push_back(fromLink); - message.links.push_back(userLink); - addPart(history->makeServiceMessage( - history->nextNonHistoryEntryId(), - MTPDmessage_ClientFlag::f_admin_log_entry, - date, - message, - MTPDmessage::Flags(0), - peerToUser(from->id))); + auto groupCallParticipantUser = [&](const MTPGroupCallParticipant &data) { + return data.match([&](const MTPDgroupCallParticipant &data) { + return history->owner().user(data.vuser_id().v); }); }; + auto addServiceMessageWithLink = [&](const QString &text, const ClickHandlerPtr &link) { + auto message = HistoryService::PreparedText{ text }; + message.links.push_back(fromLink); + message.links.push_back(link); + addPart(history->makeServiceMessage( + history->nextNonHistoryEntryId(), + MTPDmessage_ClientFlag::f_admin_log_entry, + date, + message, + MTPDmessage::Flags(0), + peerToUser(from->id))); + }; + + auto createParticipantMute = [&](const MTPDchannelAdminLogEventActionParticipantMute &data) { + const auto user = groupCallParticipantUser(data.vparticipant()); + const auto userLink = user->createOpenLink(); + const auto userLinkText = textcmdLink(2, user->name); + auto text = tr::lng_admin_log_muted_participant( + tr::now, + lt_from, + fromLinkText, + lt_user, + userLinkText); + addServiceMessageWithLink(text, userLink); + }; + auto createParticipantUnmute = [&](const MTPDchannelAdminLogEventActionParticipantUnmute &data) { - data.vparticipant().match([&](const MTPDgroupCallParticipant &data) { - const auto user = history->owner().user(data.vuser_id().v); - const auto userLink = user->createOpenLink(); - const auto userLinkText = textcmdLink(2, user->name); - auto text = tr::lng_admin_log_unmuted_participant( - tr::now, - lt_from, - fromLinkText, - lt_user, - userLinkText); - auto message = HistoryService::PreparedText{ text }; - message.links.push_back(fromLink); - message.links.push_back(userLink); - addPart(history->makeServiceMessage( - history->nextNonHistoryEntryId(), - MTPDmessage_ClientFlag::f_admin_log_entry, - date, - message, - MTPDmessage::Flags(0), - peerToUser(from->id))); - }); + const auto user = groupCallParticipantUser(data.vparticipant()); + const auto userLink = user->createOpenLink(); + const auto userLinkText = textcmdLink(2, user->name); + auto text = tr::lng_admin_log_unmuted_participant( + tr::now, + lt_from, + fromLinkText, + lt_user, + userLinkText); + addServiceMessageWithLink(text, userLink); }; auto createToggleGroupCallSetting = [&](const MTPDchannelAdminLogEventActionToggleGroupCallSetting &data) { @@ -918,6 +996,114 @@ void GenerateItems( addSimpleServiceMessage(text); }; + auto addInviteLinkServiceMessage = [&](const QString &text, const MTPExportedChatInvite &data) { + auto message = HistoryService::PreparedText{ text }; + message.links.push_back(fromLink); + if (!ExtractInviteLink(data).endsWith("...")) { + message.links.push_back(std::make_shared<UrlClickHandler>(InternalInviteLinkUrl(data))); + } + addPart(history->makeServiceMessage( + history->nextNonHistoryEntryId(), + MTPDmessage_ClientFlag::f_admin_log_entry, + date, + message, + MTPDmessage::Flags(0), + peerToUser(from->id), + nullptr)); + }; + + auto createParticipantJoinByInvite = [&](const MTPDchannelAdminLogEventActionParticipantJoinByInvite &data) { + auto text = (channel->isMegagroup() + ? tr::lng_admin_log_participant_joined_by_link + : tr::lng_admin_log_participant_joined_by_link_channel); + addInviteLinkServiceMessage( + text( + tr::now, + lt_from, + fromLinkText, + lt_link, + GenerateInviteLinkLink(data.vinvite())), + data.vinvite()); + }; + + auto createExportedInviteDelete = [&](const MTPDchannelAdminLogEventActionExportedInviteDelete &data) { + addInviteLinkServiceMessage( + tr::lng_admin_log_delete_invite_link( + tr::now, + lt_from, + fromLinkText, + lt_link, + GenerateInviteLinkLink(data.vinvite())), + data.vinvite()); + }; + + auto createExportedInviteRevoke = [&](const MTPDchannelAdminLogEventActionExportedInviteRevoke &data) { + addInviteLinkServiceMessage( + tr::lng_admin_log_revoke_invite_link( + tr::now, + lt_from, + fromLinkText, + lt_link, + GenerateInviteLinkLink(data.vinvite())), + data.vinvite()); + }; + + auto createExportedInviteEdit = [&](const MTPDchannelAdminLogEventActionExportedInviteEdit &data) { + auto bodyFlags = Flag::f_entities | Flag::f_from_id; + auto bodyClientFlags = MTPDmessage_ClientFlag::f_admin_log_entry; + auto bodyReplyTo = 0; + auto bodyViaBotId = 0; + auto bodyText = GenerateInviteLinkChangeText(data.vnew_invite(), data.vprev_invite()); + addPart(history->makeMessage( + history->nextNonHistoryEntryId(), + bodyFlags, + bodyClientFlags, + bodyReplyTo, + bodyViaBotId, + date, + peerToUser(from->id), + QString(), + bodyText)); + }; + + auto createParticipantVolume = [&](const MTPDchannelAdminLogEventActionParticipantVolume &data) { + const auto user = groupCallParticipantUser(data.vparticipant()); + const auto userLink = user->createOpenLink(); + const auto userLinkText = textcmdLink(2, user->name); + const auto volume = data.vparticipant().match([&]( + const MTPDgroupCallParticipant &data) { + return data.vvolume().value_or(10000); + }); + const auto volumeText = QString::number(volume / 100) + '%'; + auto text = tr::lng_admin_log_participant_volume( + tr::now, + lt_from, + fromLinkText, + lt_user, + userLinkText, + lt_percent, + volumeText); + addServiceMessageWithLink(text, userLink); + }; + + auto createChangeHistoryTTL = [&](const MTPDchannelAdminLogEventActionChangeHistoryTTL &data) { + const auto was = data.vprev_value().v; + const auto now = data.vnew_value().v; + const auto wrap = [](int duration) { + return (duration == 5) + ? u"5 seconds"_q + : (duration < 3 * 86400) + ? tr::lng_manage_messages_ttl_after1(tr::now) + : tr::lng_manage_messages_ttl_after2(tr::now); + }; + auto text = !was + ? tr::lng_admin_log_messages_ttl_set(tr::now, lt_from, fromLinkText, lt_duration, wrap(now)) + : !now + ? tr::lng_admin_log_messages_ttl_removed(tr::now, lt_from, fromLinkText, lt_duration, wrap(was)) + : tr::lng_admin_log_messages_ttl_changed(tr::now, lt_from, fromLinkText, lt_previous, wrap(was), lt_duration, wrap(now)); + addSimpleServiceMessage(text); + }; + action.match([&](const MTPDchannelAdminLogEventActionChangeTitle &data) { createChangeTitle(data); }, [&](const MTPDchannelAdminLogEventActionChangeAbout &data) { @@ -971,14 +1157,17 @@ void GenerateItems( }, [&](const MTPDchannelAdminLogEventActionToggleGroupCallSetting &data) { createToggleGroupCallSetting(data); }, [&](const MTPDchannelAdminLogEventActionParticipantJoinByInvite &data) { + createParticipantJoinByInvite(data); }, [&](const MTPDchannelAdminLogEventActionExportedInviteDelete &data) { + createExportedInviteDelete(data); }, [&](const MTPDchannelAdminLogEventActionExportedInviteRevoke &data) { + createExportedInviteRevoke(data); }, [&](const MTPDchannelAdminLogEventActionExportedInviteEdit &data) { - // #TODO links + createExportedInviteEdit(data); }, [&](const MTPDchannelAdminLogEventActionParticipantVolume &data) { - // #TODO calls + createParticipantVolume(data); }, [&](const MTPDchannelAdminLogEventActionChangeHistoryTTL &data) { - // #TODO ttl + createChangeHistoryTTL(data); }); }