diff --git a/Telegram/Resources/icons/chat/mini_shield_lock.svg b/Telegram/Resources/icons/chat/mini_shield_lock.svg
new file mode 100644
index 0000000000..cd3713bc3e
--- /dev/null
+++ b/Telegram/Resources/icons/chat/mini_shield_lock.svg
@@ -0,0 +1,6 @@
+
+
diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings
index 282aa4e5fc..58f0513c5f 100644
--- a/Telegram/Resources/langs/lang.strings
+++ b/Telegram/Resources/langs/lang.strings
@@ -1948,7 +1948,22 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_manage_peer_send_only_members" = "Only members";
"lng_manage_peer_send_only_members_about" = "Turn this on if you expect users to join your group before being able to send messages.";
"lng_manage_peer_send_approve_members" = "Approve new members";
-"lng_manage_peer_send_approve_members_about" = "Turn this on if you want users to join the group only after they are approved by an admin.";
+"lng_manage_peer_send_approve_subscribers" = "Approve new subscribers";
+"lng_manage_peer_send_approve_members_about" = "Admins must approve anyone who wants to send a message in the group.";
+"lng_manage_peer_send_approve_members_about_channel" = "Admins must approve anyone who wants to subscribe to this channel.";
+"lng_manage_peer_send_approve_members_about_managed" = "Admins must approve anyone who wants to send a message in the group. Managed by {bot}.";
+"lng_manage_peer_send_approve_members_about_managed_channel" = "Admins must approve anyone who wants to subscribe to this channel. Managed by {bot}.";
+"lng_manage_peer_request_apply_title" = "Apply to existing invite links";
+"lng_manage_peer_request_apply_enable_group#one" = "Also enable **Approve New Members** for **{count} existing invite link** in this group?";
+"lng_manage_peer_request_apply_enable_group#other" = "Also enable **Approve New Members** for **{count} existing invite links** in this group?";
+"lng_manage_peer_request_apply_enable_channel#one" = "Also enable **Approve New Subscribers** for **{count} existing invite link** in this channel?";
+"lng_manage_peer_request_apply_enable_channel#other" = "Also enable **Approve New Subscribers** for **{count} existing invite links** in this channel?";
+"lng_manage_peer_request_apply_disable_group#one" = "Also disable **Approve New Members** for **{count} existing invite link** in this group?";
+"lng_manage_peer_request_apply_disable_group#other" = "Also disable **Approve New Members** for **{count} existing invite links** in this group?";
+"lng_manage_peer_request_apply_disable_channel#one" = "Also disable **Approve New Subscribers** for **{count} existing invite link** in this channel?";
+"lng_manage_peer_request_apply_disable_channel#other" = "Also disable **Approve New Subscribers** for **{count} existing invite links** in this channel?";
+"lng_manage_peer_request_apply_skip" = "Don't Apply";
+"lng_manage_peer_request_apply_confirm" = "Apply";
"lng_manage_peer_no_forwards_title" = "Content protection";
"lng_manage_peer_no_forwards" = "Restrict saving content";
"lng_manage_peer_no_forwards_about" = "Members won't be able to copy, save or forward content from this group.";
@@ -2788,11 +2803,17 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_group_invite_qr_title" = "Invite by QR Code";
"lng_group_invite_qr_about" = "Everyone on Telegram can scan this code to join your group.";
"lng_group_invite_qr_copied" = "QR Code copied to clipboard.";
-"lng_group_invite_request_approve" = "Request admin approval";
-"lng_group_invite_about_approve" = "Turn this on if you want users to join only after they are approved by an admin.";
-"lng_group_invite_about_no_approve" = "Turn this on if you want users to join only after they are approved by an admin.";
-"lng_group_invite_about_approve_channel" = "Turn this on if you want users to join only after they are approved by an admin.";
-"lng_group_invite_about_no_approve_channel" = "Turn this on if you want users to join only after they are approved by an admin.";
+"lng_group_invite_request_approve" = "Approve New Members";
+"lng_group_invite_request_approve_channel" = "Approve New Subscribers";
+"lng_group_invite_about_approve" = "Require admin approval for people joining through this link.";
+"lng_group_invite_about_approve_managed" = "Require admin approval for people joining through this link. Managed by {bot}.";
+"lng_group_invite_about_no_approve" = "Require admin approval for people joining through this link.";
+"lng_group_invite_about_approve_channel" = "Require admin approval for people joining through this link.";
+"lng_group_invite_about_approve_managed_channel" = "Require admin approval for people joining through this link. Managed by {bot}.";
+"lng_group_invite_about_no_approve_channel" = "Require admin approval for people joining through this link.";
+"lng_group_invite_about_approve_disabled" = "This option is unavailable because anyone can join and send messages through the public group link.";
+"lng_group_invite_about_approve_disabled_channel" = "This option is unavailable because anyone can join the channel through the public channel link.";
+"lng_group_invite_approval_required" = "approval required";
"lng_group_invite_subscription" = "Require Monthly Fee";
"lng_group_invite_subscription_ph" = "Stars Amount per month";
"lng_group_invite_subscription_price" = "~{cost} / month";
@@ -2814,6 +2835,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_group_request_about_channel" = "This channel accepts new subscribers only after they are approved by its admins.";
"lng_group_request_sent" = "You will be added to the group once an admin approves your request.";
"lng_group_request_sent_channel" = "You will be added to the channel once its admins approve your request.";
+"lng_group_request_declined" = "Your request to join the group was declined.";
+"lng_group_request_declined_channel" = "Your request to join the channel was declined.";
"lng_group_requests_pending#one" = "{count} join request";
"lng_group_requests_pending#other" = "{count} join requests";
"lng_group_requests_pending_user" = "{user} requested to join";
@@ -6378,8 +6401,18 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_rights_dismiss_admin" = "Dismiss Admin";
"lng_rights_about_add_admins_yes" = "This admin will be able to add new admins with equal or fewer rights.";
"lng_rights_about_add_admins_no" = "This admin will not be able to add new admins.";
+"lng_rights_about_process_join_requests" = "Allow this bot to approve new members using its own interfaces, such as captchas.";
"lng_rights_about_by" = "This admin promoted by {user} on {date}.";
+"lng_guard_bot_approve_title" = "Approve New Members";
+"lng_guard_bot_approve_text" = "New members will now need approval from {bot} before they can send messages in this group.";
+"lng_guard_bot_approve_text_channel" = "New members will now need approval from {bot} before they can join this channel.";
+"lng_guard_bot_approve_confirm" = "Enable";
+"lng_guard_bot_replace_title" = "Replace Bot";
+"lng_guard_bot_replace_text" = "{from} is currently processing join requests. Switch to {to} instead?";
+"lng_guard_bot_replace_confirm" = "Use {bot}";
+"lng_guard_bot_replace_cancel" = "Keep {bot}";
+
"lng_rights_about_admin_cant_edit" = "You can't edit the rights of this admin.";
"lng_rights_about_restriction_cant_edit" = "You cannot change the restrictions for this user.";
"lng_rights_restriction_for_all" = "This option is disabled for all members in Group Permissions.";
@@ -6475,6 +6508,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_rights_group_anonymous" = "Remain anonymous";
"lng_rights_group_manage_ranks" = "Edit member tags";
"lng_rights_add_admins" = "Add new admins";
+"lng_rights_group_process_join_requests" = "Process join requests";
"lng_rights_chat_send_text" = "Send messages";
"lng_rights_chat_send_media" = "Send media";
"lng_rights_chat_send_stickers" = "Send stickers & GIFs";
diff --git a/Telegram/SourceFiles/api/api_chat_invite.cpp b/Telegram/SourceFiles/api/api_chat_invite.cpp
index 059cbfc850..f2bd7ff180 100644
--- a/Telegram/SourceFiles/api/api_chat_invite.cpp
+++ b/Telegram/SourceFiles/api/api_chat_invite.cpp
@@ -21,6 +21,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_user.h"
#include "info/channel_statistics/boosts/giveaway/boost_badge.h"
#include "info/profile/info_profile_badge.h"
+#include "inline_bots/bot_attach_web_view.h"
#include "lang/lang_keys.h"
#include "main/main_session.h"
#include "settings/settings_credits_graphics.h"
@@ -119,40 +120,52 @@ void SubmitChatInvite(
bool isGroup) {
session->api().request(MTPmessages_ImportChatInvite(
MTP_string(hash)
- )).done([=](const MTPUpdates &result) {
- session->api().applyUpdates(result);
+ )).done([=](const MTPmessages_ChatInviteJoinResult &result) {
const auto strongController = weak.get();
- if (!strongController) {
- return;
+ if (strongController) {
+ strongController->hideLayer();
}
- strongController->hideLayer();
- const auto handleChats = [&](const MTPVector &chats) {
- if (chats.v.isEmpty()) {
- return;
- }
- const auto peerId = chats.v[0].match([](const MTPDchat &data) {
- return peerFromChat(data.vid().v);
- }, [](const MTPDchannel &data) {
- return peerFromChannel(data.vid().v);
- }, [](auto&&) {
- return PeerId(0);
- });
- if (const auto peer = session->data().peerLoaded(peerId)) {
- // Shows in the primary window anyway.
- strongController->showPeerHistory(
- peer,
- Window::SectionShow::Way::Forward);
- }
- };
- result.match([&](const MTPDupdates &data) {
- handleChats(data.vchats());
- }, [&](const MTPDupdatesCombined &data) {
- handleChats(data.vchats());
- }, [&](auto &&) {
- LOG(("API Error: unexpected update cons %1 "
- "(ApiWrap::importChatInvite)").arg(result.type()));
- });
+ ProcessChatInviteJoinResult(
+ session,
+ strongController ? strongController->uiShow() : nullptr,
+ result,
+ [=](const MTPUpdates &updates) {
+ session->api().applyUpdates(updates);
+ if (!strongController) {
+ return;
+ }
+ const auto handleChats = [&](
+ const MTPVector &chats) {
+ if (chats.v.isEmpty()) {
+ return;
+ }
+ const auto peerId = chats.v[0].match(
+ [](const MTPDchat &data) {
+ return peerFromChat(data.vid().v);
+ },
+ [](const MTPDchannel &data) {
+ return peerFromChannel(data.vid().v);
+ },
+ [](auto&&) {
+ return PeerId(0);
+ });
+ if (const auto peer = session->data().peerLoaded(peerId)) {
+ strongController->showPeerHistory(
+ peer,
+ Window::SectionShow::Way::Forward);
+ }
+ };
+ updates.match([&](const MTPDupdates &data) {
+ handleChats(data.vchats());
+ }, [&](const MTPDupdatesCombined &data) {
+ handleChats(data.vchats());
+ }, [&](auto &&) {
+ LOG(("API Error: unexpected update cons %1 "
+ "(ApiWrap::importChatInvite)").arg(updates.type()));
+ });
+ },
+ weak);
}).fail([=](const MTP::Error &error) {
const auto &type = error.type();
@@ -623,6 +636,37 @@ void ConfirmInviteBox(
} // namespace
+void ProcessChatInviteJoinResult(
+ not_null session,
+ std::shared_ptr show,
+ const MTPmessages_ChatInviteJoinResult &result,
+ Fn done,
+ base::weak_ptr controller) {
+ result.match([&](const MTPDmessages_chatInviteJoinResultOk &data) {
+ done(data.vupdates());
+ }, [&](const MTPDmessages_chatInviteJoinResultWebView &data) {
+ session->data().processUsers(data.vusers());
+ const auto bot = session->data().userLoaded(UserId(data.vbot_id().v));
+ if (!bot || !show) {
+ LOG(("API Error: guard bot %1 not loaded "
+ "(Api::ProcessChatInviteJoinResult)").arg(data.vbot_id().v));
+ return;
+ }
+ session->attachWebView().open({
+ .bot = not_null{ bot },
+ .parentShow = std::move(show),
+ .context = {
+ .controller = controller,
+ .maySkipConfirmation = false,
+ },
+ .source = InlineBots::WebViewSourceJoinChat{
+ .url = qs(data.vurl()),
+ .queryId = uint64(data.vquery_id().v),
+ },
+ });
+ });
+}
+
void CheckChatInvite(
not_null controller,
const QString &hash,
diff --git a/Telegram/SourceFiles/api/api_chat_invite.h b/Telegram/SourceFiles/api/api_chat_invite.h
index dbd5a43321..ae3a580f20 100644
--- a/Telegram/SourceFiles/api/api_chat_invite.h
+++ b/Telegram/SourceFiles/api/api_chat_invite.h
@@ -7,12 +7,18 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
+#include "base/weak_ptr.h"
+
class ChannelData;
namespace Main {
class Session;
} // namespace Main
+namespace Ui {
+class Show;
+} // namespace Ui
+
namespace Window {
class SessionController;
} // namespace Window
@@ -25,4 +31,11 @@ void CheckChatInvite(
ChannelData *invitePeekChannel = nullptr,
Fn loaded = nullptr);
+void ProcessChatInviteJoinResult(
+ not_null session,
+ std::shared_ptr show,
+ const MTPmessages_ChatInviteJoinResult &result,
+ Fn done,
+ base::weak_ptr controller = {});
+
} // namespace Api
diff --git a/Telegram/SourceFiles/api/api_updates.cpp b/Telegram/SourceFiles/api/api_updates.cpp
index 95837ee07d..77d68a9b64 100644
--- a/Telegram/SourceFiles/api/api_updates.cpp
+++ b/Telegram/SourceFiles/api/api_updates.cpp
@@ -2232,6 +2232,15 @@ void Updates::feedUpdate(const MTPUpdate &update) {
session().data().webViewResultSent({ .queryId = d.vquery_id().v });
} break;
+ case mtpc_updateJoinChatWebViewDecision: {
+ const auto &d = update.c_updateJoinChatWebViewDecision();
+ session().data().joinChatWebViewDecision({
+ .peerId = peerFromMTP(d.vpeer()),
+ .queryId = uint64(d.vquery_id().v),
+ .result = d.vresult(),
+ });
+ } break;
+
case mtpc_updateBotMenuButton: {
const auto &d = update.c_updateBotMenuButton();
if (const auto bot = session().data().userLoaded(d.vbot_id())) {
diff --git a/Telegram/SourceFiles/apiwrap.cpp b/Telegram/SourceFiles/apiwrap.cpp
index 56ba19b84b..317ce13dab 100644
--- a/Telegram/SourceFiles/apiwrap.cpp
+++ b/Telegram/SourceFiles/apiwrap.cpp
@@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "api/api_authorizations.h"
#include "api/api_attached_stickers.h"
#include "api/api_blocked_peers.h"
+#include "api/api_chat_invite.h"
#include "api/api_chat_links.h"
#include "api/api_chat_participants.h"
#include "api/api_cloud_password.h"
@@ -1822,14 +1823,20 @@ void ApiWrap::joinChannel(not_null channel) {
} else if (!_channelAmInRequests.contains(channel)) {
const auto requestId = request(MTPchannels_JoinChannel(
channel->inputChannel()
- )).done([=](const MTPUpdates &result) {
+ )).done([=](const MTPmessages_ChatInviteJoinResult &result) {
_channelAmInRequests.remove(channel);
- applyUpdates(result);
- session().data().addRecentJoinChat({
- .fromPeerId = channel->id,
- .joinedPeerId = channel->id,
- });
+ Api::ProcessChatInviteJoinResult(
+ _session,
+ ShowForPeer(channel),
+ result,
+ [=](const MTPUpdates &updates) {
+ applyUpdates(updates);
+ session().data().addRecentJoinChat({
+ .fromPeerId = channel->id,
+ .joinedPeerId = channel->id,
+ });
+ });
}).fail([=](const MTP::Error &error) {
const auto &type = error.type();
diff --git a/Telegram/SourceFiles/boxes/boxes.style b/Telegram/SourceFiles/boxes/boxes.style
index 1f21bbf8fb..4273d8c9c2 100644
--- a/Telegram/SourceFiles/boxes/boxes.style
+++ b/Telegram/SourceFiles/boxes/boxes.style
@@ -1251,6 +1251,8 @@ futureOwnerBox: Box(defaultBox) {
buttonHeight: 0px;
}
futureOwnerBoxSelect: collectibleBox;
+guardBotReplaceBox: Box(futureOwnerBox) {
+}
disableSharingIconPadding: margins(12px, 12px, 12px, 8px);
disableSharingButtonLock: IconEmoji {
diff --git a/Telegram/SourceFiles/boxes/peers/edit_participant_box.cpp b/Telegram/SourceFiles/boxes/peers/edit_participant_box.cpp
index 153bcf203b..a337f9b3c4 100644
--- a/Telegram/SourceFiles/boxes/peers/edit_participant_box.cpp
+++ b/Telegram/SourceFiles/boxes/peers/edit_participant_box.cpp
@@ -34,6 +34,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "boxes/peers/add_bot_to_chat_box.h"
#include "boxes/peers/edit_peer_permissions_box.h"
#include "boxes/peers/edit_peer_info_box.h"
+#include "boxes/peers/replace_boost_box.h"
#include "settings/settings_common.h"
#include "data/data_peer_values.h"
#include "data/data_channel.h"
@@ -46,6 +47,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "styles/style_layers.h"
#include "styles/style_boxes.h"
#include "styles/style_info.h"
+#include "styles/style_premium.h"
#include "styles/style_settings.h"
namespace {
@@ -54,6 +56,108 @@ constexpr auto kMaxRestrictDelayDays = 366;
constexpr auto kSecondsInDay = 24 * 60 * 60;
constexpr auto kSecondsInWeek = 7 * kSecondsInDay;
+[[nodiscard]] bool CanProcessJoinRequests(
+ not_null peer,
+ not_null user) {
+ const auto info = user->botInfo.get();
+ return peer->isChannel()
+ && user->isBot()
+ && info
+ && info->supportsGuard;
+}
+
+[[nodiscard]] object_ptr GuardBotReplaceBox(
+ not_null current,
+ not_null replacement,
+ Fn)> confirmed) {
+ return Box([=](not_null box) {
+ box->setStyle(st::guardBotReplaceBox);
+ const auto content = box->verticalLayout();
+ const auto weak = base::make_weak(box);
+ content->add(
+ CreateUserpicsTransfer(
+ content,
+ rpl::single(std::vector{ not_null(current) }),
+ replacement,
+ UserpicsTransferType::GuardBotReplace),
+ st::boxRowPadding + st::boostReplaceUserpicsPadding);
+ content->add(
+ object_ptr(
+ content,
+ tr::lng_guard_bot_replace_title(),
+ st::boostCenteredTitle),
+ st::boxRowPadding + st::starrefJoinTitlePadding,
+ style::al_top);
+ content->add(
+ object_ptr(
+ content,
+ rpl::single(tr::lng_guard_bot_replace_text(
+ tr::now,
+ lt_from,
+ tr::bold(current->name()),
+ lt_to,
+ tr::bold(replacement->name()),
+ tr::rich)),
+ st::boxLabel),
+ QMargins(
+ st::boxPadding.left(),
+ 0,
+ st::boxPadding.right(),
+ st::boxPadding.bottom()));
+ Ui::AddSkip(content);
+ const auto use = content->add(
+ object_ptr(
+ content,
+ rpl::single(tr::lng_guard_bot_replace_confirm(
+ tr::now,
+ lt_bot,
+ replacement->shortName())),
+ st::defaultLightButton),
+ st::boxRowPadding,
+ style::al_justify);
+ Ui::AddSkip(content);
+ const auto keep = content->add(
+ object_ptr(
+ content,
+ rpl::single(tr::lng_guard_bot_replace_cancel(
+ tr::now,
+ lt_bot,
+ current->shortName())),
+ st::defaultLightButton),
+ st::boxRowPadding,
+ style::al_justify);
+ use->setClickedCallback([=] {
+ confirmed(crl::guard(weak, [=] { weak->closeBox(); }));
+ });
+ keep->setClickedCallback([=] {
+ box->closeBox();
+ });
+ });
+}
+
+[[nodiscard]] object_ptr GuardBotApproveBox(
+ bool isGroup,
+ not_null bot,
+ Fn)> confirmed) {
+ const auto text = isGroup
+ ? tr::lng_guard_bot_approve_text(
+ tr::now,
+ lt_bot,
+ tr::bold(bot->name()),
+ tr::rich)
+ : tr::lng_guard_bot_approve_text_channel(
+ tr::now,
+ lt_bot,
+ tr::bold(bot->name()),
+ tr::rich);
+ return Ui::MakeConfirmBox({
+ .text = text,
+ .confirmed = confirmed,
+ .confirmText = tr::lng_guard_bot_approve_confirm(),
+ .title = tr::lng_guard_bot_approve_title(),
+ });
+}
+
class Cover final : public Ui::FixedHeightWidget {
public:
Cover(QWidget *parent, not_null user, bool hasAdminRights);
@@ -324,7 +428,10 @@ ChatAdminRightsInfo EditAdminBox::defaultRights() const {
| Flag::ManageTopics
| Flag::PinMessages
| Flag::ManageCall
- | Flag::ManageRanks) }
+ | Flag::ManageRanks
+ | (CanProcessJoinRequests(peer(), user())
+ ? Flag::ProcessJoinRequests
+ : Flag())) }
: ChatAdminRightsInfo{ (Flag::ChangeInfo
| Flag::PostMessages
| Flag::EditMessages
@@ -410,6 +517,15 @@ void EditAdminBox::prepare() {
const auto chat = peer()->asChat();
const auto channel = peer()->asChannel();
+ const auto supportsProcessJoinRequests = canSave()
+ && CanProcessJoinRequests(peer(), user());
+ const auto canProcessJoinRequests = supportsProcessJoinRequests
+ && peer()->isMegagroup();
+ const auto guardBotRight = (canProcessJoinRequests
+ && channel
+ && (channel->guardBotId() == peerToUser(user()->id)))
+ ? Flag::ProcessJoinRequests
+ : Flag();
const auto prepareRights = _addingBot
? ChatAdminRightsInfo(_oldRights.flags | _addingBot->existing)
: _oldRights.flags
@@ -422,9 +538,16 @@ void EditAdminBox::prepare() {
&& !_oldRights.flags
&& channel
&& !channel->amCreator();
+ const auto myEditableRights = channel
+ ? (channel->adminRights()
+ | (canProcessJoinRequests
+ ? Flag::ProcessJoinRequests
+ : Flag()))
+ : Flags();
const auto prepareFlags = disabledByDefaults
+ | guardBotRight
| (prepareRights.flags
- & (filterByMyRights ? channel->adminRights() : ~Flag(0)));
+ & (filterByMyRights ? myEditableRights : ~Flag(0)));
const auto disabledMessages = [&] {
auto result = base::flat_map();
@@ -443,7 +566,7 @@ void EditAdminBox::prepare() {
} else if (const auto channel = peer()->asChannel()) {
if (!channel->amCreator()) {
result.emplace(
- ~channel->adminRights(),
+ ~myEditableRights,
tr::lng_rights_permission_cant_edit(tr::now));
}
}
@@ -459,6 +582,7 @@ void EditAdminBox::prepare() {
.isGroup = isGroup,
.isForum = peer()->isForum(),
.anyoneCanAddMembers = anyoneCanAddMembers,
+ .canProcessJoinRequests = canProcessJoinRequests,
};
Ui::AddSubsectionTitle(inner, tr::lng_rights_edit_admin_header());
auto [checkboxes, getChecked, changes, highlightWidget] = CreateEditAdminRights(
@@ -492,26 +616,33 @@ void EditAdminBox::prepare() {
Ui::AddDivider(emptyAboutAddAdminsInner->entity());
}
Ui::AddSkip(aboutAddAdminsInner->entity());
- Ui::AddDividerText(
- aboutAddAdminsInner->entity(),
- rpl::duplicate(
- selectedFlags
- ) | rpl::map(
- (_1 & Flag::AddAdmins) != 0
- ) | rpl::distinct_until_changed(
- ) | rpl::map([=](bool canAddAdmins) -> rpl::producer {
- const auto empty = (amCreator() && user()->isSelf());
- aboutAddAdminsInner->toggle(!empty, anim::type::instant);
- emptyAboutAddAdminsInner->toggle(empty, anim::type::instant);
- if (empty) {
- return rpl::single(QString());
- } else if (!canSave()) {
- return tr::lng_rights_about_admin_cant_edit();
- } else if (canAddAdmins) {
- return tr::lng_rights_about_add_admins_yes();
- }
- return tr::lng_rights_about_add_admins_no();
- }) | rpl::flatten_latest());
+ if (canProcessJoinRequests) {
+ aboutAddAdminsInner->toggle(true, anim::type::instant);
+ Ui::AddDividerText(
+ aboutAddAdminsInner->entity(),
+ tr::lng_rights_about_process_join_requests());
+ } else {
+ Ui::AddDividerText(
+ aboutAddAdminsInner->entity(),
+ rpl::duplicate(
+ selectedFlags
+ ) | rpl::map(
+ (_1 & Flag::AddAdmins) != 0
+ ) | rpl::distinct_until_changed(
+ ) | rpl::map([=](bool canAddAdmins) -> rpl::producer {
+ const auto empty = (amCreator() && user()->isSelf());
+ aboutAddAdminsInner->toggle(!empty, anim::type::instant);
+ emptyAboutAddAdminsInner->toggle(empty, anim::type::instant);
+ if (empty) {
+ return rpl::single(QString());
+ } else if (!canSave()) {
+ return tr::lng_rights_about_admin_cant_edit();
+ } else if (canAddAdmins) {
+ return tr::lng_rights_about_add_admins_yes();
+ }
+ return tr::lng_rights_about_add_admins_no();
+ }) | rpl::flatten_latest());
+ }
}
if (canTransferOwnership()) {
@@ -590,13 +721,15 @@ void EditAdminBox::prepare() {
const auto newFlags = (value() | ChatAdminRight::Other)
& ((!channel || channel->amCreator())
? ~Flags(0)
- : channel->adminRights());
- _saveCallback(
- _oldRights,
- ChatAdminRightsInfo(newFlags),
- _tagControl
- ? std::optional(_tagControl->currentRank())
- : std::nullopt);
+ : myEditableRights);
+ confirmGuardBotSave(ChatAdminRightsInfo(newFlags), [=] {
+ _saveCallback(
+ _oldRights,
+ ChatAdminRightsInfo(newFlags),
+ _tagControl
+ ? std::optional(_tagControl->currentRank())
+ : std::nullopt);
+ });
};
_save = [=] {
const auto show = uiShow();
@@ -631,9 +764,44 @@ void EditAdminBox::prepare() {
}
void EditAdminBox::finishAddAdmin() {
+ const auto confirmBox = _confirmBox;
+ _confirmBox = nullptr;
+ if (confirmBox) {
+ confirmBox->closeBox();
+ }
_finishSave();
- if (_confirmBox) {
- _confirmBox->closeBox();
+}
+
+void EditAdminBox::confirmGuardBotSave(
+ ChatAdminRightsInfo rights,
+ Fn done) {
+ using Flag = ChatAdminRight;
+
+ const auto channel = peer()->asChannel();
+ if (!channel
+ || !(rights.flags & Flag::ProcessJoinRequests)
+ || !CanProcessJoinRequests(peer(), user())) {
+ done();
+ return;
+ }
+
+ const auto confirmed = crl::guard(this, [=](Fn close) {
+ close();
+ done();
+ });
+ const auto current = channel->guardBot();
+ if (current && current != user().get()) {
+ _confirmBox = getDelegate()->show(GuardBotReplaceBox(
+ current,
+ user(),
+ confirmed));
+ } else if (!channel->requestToJoin()) {
+ _confirmBox = getDelegate()->show(GuardBotApproveBox(
+ channel->isMegagroup(),
+ user(),
+ confirmed));
+ } else {
+ done();
}
}
diff --git a/Telegram/SourceFiles/boxes/peers/edit_participant_box.h b/Telegram/SourceFiles/boxes/peers/edit_participant_box.h
index b3c1df3cfb..d9931cf02b 100644
--- a/Telegram/SourceFiles/boxes/peers/edit_participant_box.h
+++ b/Telegram/SourceFiles/boxes/peers/edit_participant_box.h
@@ -101,6 +101,7 @@ private:
return _saveCallback != nullptr;
}
void finishAddAdmin();
+ void confirmGuardBotSave(ChatAdminRightsInfo rights, Fn done);
void refreshButtons();
[[nodiscard]] bool canTransferOwnership() const;
not_null*> setupTransferButton(
diff --git a/Telegram/SourceFiles/boxes/peers/edit_participants_box.cpp b/Telegram/SourceFiles/boxes/peers/edit_participants_box.cpp
index 8f59876791..8b0fe0e0ff 100644
--- a/Telegram/SourceFiles/boxes/peers/edit_participants_box.cpp
+++ b/Telegram/SourceFiles/boxes/peers/edit_participants_box.cpp
@@ -200,6 +200,72 @@ void SaveChannelAdmin(
}).send();
}
+[[nodiscard]] ChatAdminRightsInfo StripLocalAdminRights(
+ ChatAdminRightsInfo rights) {
+ rights.flags &= ~ChatAdminRight::ProcessJoinRequests;
+ return rights;
+}
+
+void SaveGuardBot(
+ std::shared_ptr show,
+ not_null channel,
+ not_null user,
+ bool processJoinRequests,
+ Fn onDone,
+ Fn onFail) {
+ const auto info = user->botInfo.get();
+ const auto userId = peerToUser(user->id);
+ const auto guardBotId = channel->guardBotId();
+ if (!info || !info->supportsGuard) {
+ if (onDone) {
+ onDone();
+ }
+ return;
+ } else if (processJoinRequests
+ && channel->requestToJoin()
+ && guardBotId == userId) {
+ if (onDone) {
+ onDone();
+ }
+ return;
+ } else if (!processJoinRequests && guardBotId != userId) {
+ if (onDone) {
+ onDone();
+ }
+ return;
+ }
+
+ using Flag = MTPchannels_ToggleJoinRequest::Flag;
+ const auto keepRequests = processJoinRequests || channel->requestToJoin();
+ channel->session().api().request(MTPchannels_ToggleJoinRequest(
+ MTP_flags(Flag::f_guard_bot),
+ channel->inputChannel(),
+ MTP_bool(keepRequests),
+ processJoinRequests ? user->inputUser() : MTP_inputUserEmpty()
+ )).done([=](const MTPUpdates &result) {
+ channel->session().api().applyUpdates(result);
+ if (processJoinRequests) {
+ channel->addFlags(ChannelDataFlag::RequestToJoin);
+ channel->setGuardBotId(userId);
+ } else {
+ if (!keepRequests) {
+ channel->removeFlags(ChannelDataFlag::RequestToJoin);
+ }
+ channel->setGuardBotId(UserId());
+ }
+ if (onDone) {
+ onDone();
+ }
+ }).fail([=](const MTP::Error &error) {
+ if (show) {
+ show->showToast(error.type());
+ }
+ if (onFail) {
+ onFail();
+ }
+ }).send();
+}
+
void SaveChatParticipantKick(
std::shared_ptr show,
not_null chat,
@@ -292,16 +358,38 @@ Fn &rank) {
- const auto done = [=] { if (onDone) onDone(newRights, rank); };
+ const auto processJoinRequests = ((newRights.flags
+ & ChatAdminRight::ProcessJoinRequests) != 0);
+ const auto manageGuardBot = peer->isMegagroup()
+ || processJoinRequests;
+ const auto strippedOldRights = StripLocalAdminRights(oldRights);
+ const auto strippedNewRights = StripLocalAdminRights(newRights);
+ const auto done = [=] {
+ if (onDone) {
+ onDone(strippedNewRights, rank);
+ }
+ };
const auto saveForChannel = [=](not_null channel) {
SaveChannelAdmin(
show,
channel,
user,
- oldRights,
- newRights,
+ strippedOldRights,
+ strippedNewRights,
rank,
- done,
+ [=] {
+ if (manageGuardBot) {
+ SaveGuardBot(
+ show,
+ channel,
+ user,
+ processJoinRequests,
+ done,
+ onFail);
+ } else {
+ done();
+ }
+ },
onFail);
};
if (const auto chat = peer->asChatNotMigrated()) {
@@ -311,9 +399,10 @@ FndefaultAdminRights(user).flags) {
+ if (strippedNewRights.flags
+ == chat->defaultAdminRights(user).flags) {
saveChatAdmin(true);
- } else if (!newRights.flags) {
+ } else if (!strippedNewRights.flags) {
saveChatAdmin(false);
} else {
peer->session().api().migrateChat(chat, saveForChannel);
diff --git a/Telegram/SourceFiles/boxes/peers/edit_peer_info_box.cpp b/Telegram/SourceFiles/boxes/peers/edit_peer_info_box.cpp
index f14dd6a664..0427daf8a1 100644
--- a/Telegram/SourceFiles/boxes/peers/edit_peer_info_box.cpp
+++ b/Telegram/SourceFiles/boxes/peers/edit_peer_info_box.cpp
@@ -407,6 +407,7 @@ private:
std::optional noForwards;
std::optional joinToWrite;
std::optional requestToJoin;
+ std::optional requestToJoinApplyToInvites;
std::optional discussionLink;
std::optional starsPerDirectMessage;
};
@@ -989,6 +990,10 @@ void Controller::fillPrivacyTypeButton() {
// Create Privacy Button.
const auto hasLocation = _peer->isChannel()
&& _peer->asChannel()->hasLocation();
+ const auto guardBot = _peer->isChannel()
+ ? _peer->asChannel()->guardBot()
+ : nullptr;
+ const auto guardBotUsername = guardBot ? guardBot->username() : QString();
_typeDataSavedValue = EditPeerTypeData{
.privacy = ((_peer->isChannel()
&& _peer->asChannel()->hasUsername())
@@ -1003,8 +1008,12 @@ void Controller::fillPrivacyTypeButton() {
.noForwards = !_peer->allowsForwarding(),
.joinToWrite = (_peer->isMegagroup()
&& _peer->asChannel()->joinToWrite()),
- .requestToJoin = (_peer->isMegagroup()
+ .requestToJoin = (_peer->isChannel()
&& _peer->asChannel()->requestToJoin()),
+ .guardBotUsername = guardBotUsername,
+ .guardBotLink = guardBotUsername.isEmpty()
+ ? QString()
+ : _peer->session().createInternalLink(guardBotUsername),
};
const auto isGroup = (_peer->isChat() || _peer->isMegagroup());
AddButtonWithText(
@@ -2233,6 +2242,8 @@ bool Controller::validateRequestToJoin(Saving &to) const {
return true;
}
to.requestToJoin = _typeDataSavedValue->requestToJoin;
+ to.requestToJoinApplyToInvites
+ = _typeDataSavedValue->requestToJoinApplyToInvites;
return true;
}
@@ -2792,15 +2803,22 @@ void Controller::saveJoinToWrite() {
}
void Controller::saveRequestToJoin() {
- const auto requestToJoin = _peer->isMegagroup()
+ const auto requestToJoin = _peer->isChannel()
&& _peer->asChannel()->requestToJoin();
if (!_savingData.requestToJoin
|| *_savingData.requestToJoin == requestToJoin) {
return continueSave();
}
+ using Flag = MTPchannels_ToggleJoinRequest::Flag;
+ const auto channel = _peer->asChannel();
+ const auto flags = _savingData.requestToJoinApplyToInvites.value_or(false)
+ ? Flag::f_apply_to_invites
+ : Flag();
_api.request(MTPchannels_ToggleJoinRequest(
- _peer->asChannel()->inputChannel(),
- MTP_bool(*_savingData.requestToJoin)
+ MTP_flags(flags),
+ channel->inputChannel(),
+ MTP_bool(*_savingData.requestToJoin),
+ MTP_inputUserEmpty()
)).done([=](const MTPUpdates &result) {
_peer->session().api().applyUpdates(result);
continueSave();
diff --git a/Telegram/SourceFiles/boxes/peers/edit_peer_invite_link.cpp b/Telegram/SourceFiles/boxes/peers/edit_peer_invite_link.cpp
index ac5b632317..d4f61cd0e9 100644
--- a/Telegram/SourceFiles/boxes/peers/edit_peer_invite_link.cpp
+++ b/Telegram/SourceFiles/boxes/peers/edit_peer_invite_link.cpp
@@ -1619,6 +1619,16 @@ object_ptr EditLinkBox(
};
const auto isGroup = !peer->isBroadcast();
const auto isPublic = peer->isChannel() && peer->asChannel()->isPublic();
+ const auto globalRequestApproval = peer->isChannel()
+ && peer->asChannel()->requestToJoin();
+ const auto guardBot = [&]() -> UserData* {
+ const auto channel = peer->asChannel();
+ return channel ? channel->guardBot() : nullptr;
+ }();
+ const auto guardBotUsername = guardBot ? guardBot->username() : QString();
+ const auto guardBotLink = guardBotUsername.isEmpty()
+ ? QString()
+ : peer->session().createInternalLink(guardBotUsername);
auto object = Box([=](not_null box) {
const auto fill = isGroup
? Fn(nullptr)
@@ -1626,7 +1636,15 @@ object_ptr EditLinkBox(
return Ui::FillCreateInviteLinkSubscriptionToggle(box, peer);
};
if (creating) {
- Ui::CreateInviteLinkBox(box, fill, isGroup, isPublic, done);
+ Ui::CreateInviteLinkBox(
+ box,
+ fill,
+ isGroup,
+ isPublic,
+ globalRequestApproval,
+ guardBotUsername,
+ guardBotLink,
+ done);
} else {
Ui::EditInviteLinkBox(
box,
@@ -1640,6 +1658,9 @@ object_ptr EditLinkBox(
.requestApproval = data.requestApproval,
.isGroup = isGroup,
.isPublic = isPublic,
+ .globalRequestApproval = globalRequestApproval,
+ .guardBotUsername = guardBotUsername,
+ .guardBotLink = guardBotLink,
},
done);
}
diff --git a/Telegram/SourceFiles/boxes/peers/edit_peer_invite_links.cpp b/Telegram/SourceFiles/boxes/peers/edit_peer_invite_links.cpp
index eacaa448ca..9809b8fae8 100644
--- a/Telegram/SourceFiles/boxes/peers/edit_peer_invite_links.cpp
+++ b/Telegram/SourceFiles/boxes/peers/edit_peer_invite_links.cpp
@@ -209,6 +209,9 @@ private:
add(QLocale().toString(time, QLocale::LongFormat));
}
}
+ if (link.requestApproval) {
+ add(tr::lng_group_invite_approval_required(tr::now));
+ }
return result;
}
diff --git a/Telegram/SourceFiles/boxes/peers/edit_peer_permissions_box.cpp b/Telegram/SourceFiles/boxes/peers/edit_peer_permissions_box.cpp
index b74ebd6841..fdb54d3a39 100644
--- a/Telegram/SourceFiles/boxes/peers/edit_peer_permissions_box.cpp
+++ b/Telegram/SourceFiles/boxes/peers/edit_peer_permissions_box.cpp
@@ -144,6 +144,12 @@ constexpr auto kDefaultChargeStars = 10;
{ Flag::Anonymous, tr::lng_rights_group_anonymous(tr::now) },
{ Flag::AddAdmins, tr::lng_rights_add_admins(tr::now) },
};
+ if (options.canProcessJoinRequests) {
+ second.push_back({
+ Flag::ProcessJoinRequests,
+ tr::lng_rights_group_process_join_requests(tr::now),
+ });
+ }
if (!options.isForum) {
first.erase(
ranges::remove(
@@ -181,6 +187,12 @@ constexpr auto kDefaultChargeStars = 10;
{ Flag::AddAdmins, tr::lng_rights_add_admins(tr::now) },
{ Flag::BanUsers, tr::lng_rights_group_ban(tr::now) },
};
+ if (options.canProcessJoinRequests) {
+ second.push_back({
+ Flag::ProcessJoinRequests,
+ tr::lng_rights_group_process_join_requests(tr::now),
+ });
+ }
return {
{ std::nullopt, std::move(first) },
{ tr::lng_rights_channel_manage(), std::move(messages) },
@@ -1467,7 +1479,9 @@ ChatAdminRights AdminRightsForOwnershipTransfer(
Data::AdminRightsSetOptions options) {
auto result = ChatAdminRights();
for (const auto &entry : AdminRightLabels(options)) {
- if (!(entry.flags & ChatAdminRight::Anonymous)) {
+ if (!(entry.flags
+ & (ChatAdminRight::Anonymous
+ | ChatAdminRight::ProcessJoinRequests))) {
result |= entry.flags;
}
}
diff --git a/Telegram/SourceFiles/boxes/peers/edit_peer_type_box.cpp b/Telegram/SourceFiles/boxes/peers/edit_peer_type_box.cpp
index 0373cc6a16..d883cfa9cd 100644
--- a/Telegram/SourceFiles/boxes/peers/edit_peer_type_box.cpp
+++ b/Telegram/SourceFiles/boxes/peers/edit_peer_type_box.cpp
@@ -36,6 +36,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/widgets/fields/input_field.h"
#include "ui/widgets/labels.h"
#include "ui/widgets/box_content_divider.h"
+#include "ui/text/text_utilities.h"
#include "ui/wrap/padding_wrap.h"
#include "ui/wrap/slide_wrap.h"
#include "ui/wrap/vertical_layout.h"
@@ -49,6 +50,68 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
namespace {
+[[nodiscard]] TextWithEntities RequestToJoinAbout(
+ bool joinToWrite,
+ bool requestToJoin,
+ bool isGroup,
+ const QString &guardBotUsername,
+ const QString &guardBotLink) {
+ if (!joinToWrite) {
+ return TextWithEntities{
+ tr::lng_manage_peer_send_only_members_about(tr::now)
+ };
+ } else if (requestToJoin && !guardBotUsername.isEmpty()) {
+ return (isGroup
+ ? tr::lng_manage_peer_send_approve_members_about_managed
+ : tr::lng_manage_peer_send_approve_members_about_managed_channel)(
+ tr::now,
+ lt_bot,
+ tr::link('@' + guardBotUsername, guardBotLink),
+ tr::marked);
+ }
+ return TextWithEntities{
+ (isGroup
+ ? tr::lng_manage_peer_send_approve_members_about
+ : tr::lng_manage_peer_send_approve_members_about_channel)(
+ tr::now)
+ };
+}
+
+[[nodiscard]] TextWithEntities ApplyRequestToJoinToInvitesText(
+ bool enabled,
+ bool isGroup,
+ int count) {
+ if (enabled) {
+ return (isGroup
+ ? tr::lng_manage_peer_request_apply_enable_group
+ : tr::lng_manage_peer_request_apply_enable_channel)(
+ tr::now,
+ lt_count,
+ count,
+ tr::rich);
+ }
+ return (isGroup
+ ? tr::lng_manage_peer_request_apply_disable_group
+ : tr::lng_manage_peer_request_apply_disable_channel)(
+ tr::now,
+ lt_count,
+ count,
+ tr::rich);
+}
+
+[[nodiscard]] int InviteLinksCount(
+ const MTPmessages_ChatAdminsWithInvites &result) {
+ auto count = 0;
+ result.match([&](const MTPDmessages_chatAdminsWithInvites &data) {
+ for (const auto &admin : data.vadmins().v) {
+ admin.match([&](const MTPDchatAdminWithInvites &data) {
+ count += data.vinvites_count().v;
+ });
+ }
+ });
+ return count;
+}
+
class Controller : public base::has_weak_ptr {
public:
Controller(
@@ -87,13 +150,17 @@ public:
return _controls.joinToWrite && _controls.joinToWrite->toggled();
}
[[nodiscard]] bool requestToJoin() const {
- return _controls.requestToJoin && _controls.requestToJoin->toggled();
+ return _controls.requestToJoin
+ && (_isGroup || getPrivacy() != Privacy::HasUsername)
+ && _controls.requestToJoin->toggled();
}
[[nodiscard]] rpl::producer scrollToRequests() const {
return _scrollToRequests.events();
}
+ void submit(Fn done);
+
void showError(rpl::producer text) {
_controls.usernameInput->showError();
showUsernameError(std::move(text));
@@ -122,6 +189,7 @@ private:
object_ptr createInviteLinkBlock();
void privacyChanged(Privacy value);
+ [[nodiscard]] bool requestsSectionShown(Privacy value) const;
void checkUsernameAvailability();
void askUsernameRevoke();
@@ -130,6 +198,12 @@ private:
void showUsernameGood();
void showUsernamePending();
void showUsernameEmpty();
+ [[nodiscard]] EditPeerTypeData collectData() const;
+ void requestInviteLinksCount(Fn done);
+ void confirmApplyToInviteLinks(
+ EditPeerTypeData data,
+ int count,
+ Fn done);
void fillPrivaciesButtons(
not_null parent,
@@ -152,6 +226,7 @@ private:
bool _useLocationPhrases = false;
bool _isGroup = false;
bool _goodUsername = false;
+ bool _originalRequestToJoin = false;
base::unique_qptr _wrap;
base::Timer _checkUsernameTimer;
@@ -185,11 +260,106 @@ Controller::Controller(
, _goodUsername(_dataSavedValue
? !_dataSavedValue->username.isEmpty()
: (_peer->isChannel() && !_peer->asChannel()->editableUsername().isEmpty()))
+, _originalRequestToJoin(_dataSavedValue
+ ? _dataSavedValue->requestToJoin
+ : false)
, _wrap(container)
, _checkUsernameTimer([=] { checkUsernameAvailability(); }) {
_peer->updateFull();
}
+EditPeerTypeData Controller::collectData() const {
+ const auto privacy = getPrivacy();
+ return EditPeerTypeData{
+ .privacy = privacy,
+ .username = (privacy == Privacy::HasUsername
+ ? getUsernameInput()
+ : QString()),
+ .usernamesOrder = (privacy == Privacy::HasUsername
+ ? usernamesOrder()
+ : std::vector()),
+ .hasDiscussionLink = _dataSavedValue
+ ? _dataSavedValue->hasDiscussionLink
+ : false,
+ .noForwards = noForwards(),
+ .joinToWrite = joinToWrite(),
+ .requestToJoin = requestToJoin(),
+ .requestToJoinApplyToInvites = _dataSavedValue
+ ? _dataSavedValue->requestToJoinApplyToInvites
+ : std::optional(),
+ .guardBotUsername = _dataSavedValue
+ ? _dataSavedValue->guardBotUsername
+ : QString(),
+ .guardBotLink = _dataSavedValue
+ ? _dataSavedValue->guardBotLink
+ : QString(),
+ };
+}
+
+void Controller::submit(Fn done) {
+ const auto privacy = getPrivacy();
+ if ((privacy == Privacy::HasUsername) && !goodUsername()) {
+ if (!getUsernameInput().isEmpty() || usernamesOrder().empty()) {
+ setFocusUsername();
+ return;
+ }
+ }
+
+ const auto data = collectData();
+ if (!_dataSavedValue
+ || !_peer->isChannel()
+ || _originalRequestToJoin == data.requestToJoin) {
+ done(data);
+ return;
+ }
+
+ requestInviteLinksCount([=](int count) {
+ if (count > 0) {
+ confirmApplyToInviteLinks(data, count, done);
+ } else {
+ done(data);
+ }
+ });
+}
+
+void Controller::requestInviteLinksCount(Fn done) {
+ _api.request(MTPmessages_GetAdminsWithInvites(
+ _peer->input()
+ )).done([=](const MTPmessages_ChatAdminsWithInvites &result) {
+ done(InviteLinksCount(result));
+ }).fail([=] {
+ done(0);
+ }).send();
+}
+
+void Controller::confirmApplyToInviteLinks(
+ EditPeerTypeData data,
+ int count,
+ Fn done) {
+ const auto finish = [=](bool apply) {
+ auto result = data;
+ result.requestToJoinApplyToInvites = apply;
+ done(result);
+ };
+ _show->showBox(Ui::MakeConfirmBox({
+ .text = ApplyRequestToJoinToInvitesText(
+ data.requestToJoin,
+ _isGroup,
+ count),
+ .confirmed = [=](Fn close) {
+ close();
+ finish(true);
+ },
+ .cancelled = [=](Fn close) {
+ close();
+ finish(false);
+ },
+ .confirmText = tr::lng_manage_peer_request_apply_confirm(),
+ .cancelText = tr::lng_manage_peer_request_apply_skip(),
+ .title = tr::lng_manage_peer_request_apply_title(),
+ }));
+}
+
void Controller::createContent() {
_controls = Controls();
@@ -212,7 +382,7 @@ void Controller::createContent() {
using namespace Settings;
if (!_linkOnly) {
- if (_peer->isMegagroup()) {
+ if (_peer->isChannel()) {
_controls.whoSendWrap = _wrap->add(
object_ptr>(
_wrap.get(),
@@ -220,7 +390,7 @@ void Controller::createContent() {
const auto wrap = _controls.whoSendWrap->entity();
Ui::AddSkip(wrap);
- if (_dataSavedValue->hasDiscussionLink) {
+ if (_peer->isMegagroup() && _dataSavedValue->hasDiscussionLink) {
Ui::AddSubsectionTitle(wrap, tr::lng_manage_peer_send_title());
_controls.joinToWrite = wrap->add(EditPeerInfoBox::CreateButton(
@@ -239,7 +409,7 @@ void Controller::createContent() {
}, wrap->lifetime());
} else {
_controls.whoSendWrap->toggle(
- (_controls.privacy->current() == Privacy::HasUsername),
+ true,
anim::type::instant);
}
auto joinToWrite = _controls.joinToWrite
@@ -251,7 +421,9 @@ void Controller::createContent() {
wrap,
EditPeerInfoBox::CreateButton(
wrap,
- tr::lng_manage_peer_send_approve_members(),
+ (_isGroup
+ ? tr::lng_manage_peer_send_approve_members
+ : tr::lng_manage_peer_send_approve_subscribers)(),
rpl::single(QString()),
[=] {},
st::peerPermissionsButton,
@@ -264,14 +436,22 @@ void Controller::createContent() {
) | rpl::on_next([=](bool toggled) {
_dataSavedValue->requestToJoin = toggled;
}, wrap->lifetime());
+ auto requestToJoin = _controls.requestToJoin->toggledValue();
Ui::AddSkip(wrap);
Ui::AddDividerText(
wrap,
- rpl::conditional(
+ rpl::combine(
std::move(joinToWrite),
- tr::lng_manage_peer_send_approve_members_about(),
- tr::lng_manage_peer_send_only_members_about()));
+ std::move(requestToJoin)
+ ) | rpl::map([=](bool join, bool approval) {
+ return RequestToJoinAbout(
+ join,
+ approval,
+ _isGroup,
+ _dataSavedValue->guardBotUsername,
+ _dataSavedValue->guardBotLink);
+ }));
}
Ui::AddSkip(_wrap.get());
Ui::AddSubsectionTitle(
@@ -313,6 +493,11 @@ void Controller::createContent() {
_controls.usernameWrap->toggle(
(forShowing == Privacy::HasUsername),
anim::type::instant);
+ if (_controls.whoSendWrap) {
+ _controls.whoSendWrap->toggle(
+ requestsSectionShown(forShowing),
+ anim::type::instant);
+ }
}
}
@@ -498,8 +683,7 @@ void Controller::privacyChanged(Privacy value) {
return;
}
_controls.whoSendWrap->toggle(
- (value == Privacy::HasUsername
- || (_dataSavedValue && _dataSavedValue->hasDiscussionLink)),
+ requestsSectionShown(value),
anim::type::instant);
};
const auto refreshVisibilities = [&] {
@@ -537,6 +721,10 @@ void Controller::privacyChanged(Privacy value) {
setFocusUsername();
}
+bool Controller::requestsSectionShown(Privacy value) const {
+ return _isGroup || value != Privacy::HasUsername;
+}
+
void Controller::checkUsernameAvailability() {
if (!_controls.usernameInput) {
return;
@@ -759,29 +947,11 @@ void EditPeerTypeBox::prepare() {
if (_savedCallback.has_value()) {
addButton(tr::lng_settings_save(), [=] {
- const auto v = controller->getPrivacy();
- if ((v == Privacy::HasUsername) && !controller->goodUsername()) {
- if (!controller->getUsernameInput().isEmpty()
- || controller->usernamesOrder().empty()) {
- controller->setFocusUsername();
- return;
- }
- }
-
- auto local = std::move(*_savedCallback);
- local(EditPeerTypeData{
- .privacy = v,
- .username = (v == Privacy::HasUsername
- ? controller->getUsernameInput()
- : QString()),
- .usernamesOrder = (v == Privacy::HasUsername
- ? controller->usernamesOrder()
- : std::vector()),
- .noForwards = controller->noForwards(),
- .joinToWrite = controller->joinToWrite(),
- .requestToJoin = controller->requestToJoin(),
- }); // We don't need username with private type.
- closeBox();
+ controller->submit(crl::guard(this, [=](EditPeerTypeData data) {
+ auto local = std::move(*_savedCallback);
+ local(std::move(data));
+ closeBox();
+ }));
});
addButton(tr::lng_cancel(), [=] { closeBox(); });
} else {
diff --git a/Telegram/SourceFiles/boxes/peers/edit_peer_type_box.h b/Telegram/SourceFiles/boxes/peers/edit_peer_type_box.h
index 7b9aba6f9a..fefedb1984 100644
--- a/Telegram/SourceFiles/boxes/peers/edit_peer_type_box.h
+++ b/Telegram/SourceFiles/boxes/peers/edit_peer_type_box.h
@@ -41,6 +41,9 @@ struct EditPeerTypeData {
bool noForwards = false;
bool joinToWrite = false;
bool requestToJoin = false;
+ std::optional requestToJoinApplyToInvites;
+ QString guardBotUsername;
+ QString guardBotLink;
};
class EditPeerTypeBox : public Ui::BoxContent {
diff --git a/Telegram/SourceFiles/boxes/peers/replace_boost_box.cpp b/Telegram/SourceFiles/boxes/peers/replace_boost_box.cpp
index 1b5c3fde27..4298a8c2b3 100644
--- a/Telegram/SourceFiles/boxes/peers/replace_boost_box.cpp
+++ b/Telegram/SourceFiles/boxes/peers/replace_boost_box.cpp
@@ -695,12 +695,16 @@ object_ptr CreateUserpicsTransfer(
const auto last = state->buttons.back().get();
if (type != Type::AuctionRecipient) {
const auto boosting = (type == Type::BoostReplace);
- const auto back = boosting ? last : right;
+ const auto guard = (type == Type::GuardBotReplace);
+ const auto gradient = boosting || guard;
+ const auto back = gradient ? last : right;
const auto add = st::boostReplaceIconAdd;
- const auto &icon = boosting
+ const auto &icon = guard
+ ? st::guardBotReplaceIcon
+ : boosting
? st::boostReplaceIcon
: st::starrefJoinIcon;
- const auto skip = boosting ? st::boostReplaceIconSkip : 0;
+ const auto skip = gradient ? st::boostReplaceIconSkip : 0;
const auto w = icon.width() + 2 * skip;
const auto h = icon.height() + 2 * skip;
const auto x = back->x() + back->width() - w + add.x();
@@ -1110,4 +1114,3 @@ object_ptr CreateGiftTransfer(
}, overlay->lifetime());
return result;
}
-
diff --git a/Telegram/SourceFiles/boxes/peers/replace_boost_box.h b/Telegram/SourceFiles/boxes/peers/replace_boost_box.h
index a505799c89..3c4d7c9567 100644
--- a/Telegram/SourceFiles/boxes/peers/replace_boost_box.h
+++ b/Telegram/SourceFiles/boxes/peers/replace_boost_box.h
@@ -66,6 +66,7 @@ enum class UserpicsTransferType {
StarRefJoin,
AuctionRecipient,
ChannelFutureOwner,
+ GuardBotReplace,
};
[[nodiscard]] object_ptr CreateUserpicsTransfer(
not_null parent,
diff --git a/Telegram/SourceFiles/data/data_channel.cpp b/Telegram/SourceFiles/data/data_channel.cpp
index 4aee47b812..8687dadc9f 100644
--- a/Telegram/SourceFiles/data/data_channel.cpp
+++ b/Telegram/SourceFiles/data/data_channel.cpp
@@ -1208,6 +1208,16 @@ TimeId ChannelData::subscriptionUntilDate() const {
return _subscriptionUntilDate;
}
+UserData *ChannelData::guardBot() const {
+ return _guardBotId
+ ? owner().userLoaded(_guardBotId)
+ : nullptr;
+}
+
+void ChannelData::setGuardBotId(UserId userId) {
+ _guardBotId = userId;
+}
+
void ChannelData::updateSubscriptionUntilDate(TimeId subscriptionUntilDate) {
_subscriptionUntilDate = subscriptionUntilDate;
}
@@ -1277,6 +1287,7 @@ void ApplyChannelUpdate(
channel->setMessagesTTL(update.vttl_period().value_or_empty());
channel->setStarsPerMessage(
update.vsend_paid_messages_stars().value_or_empty());
+ channel->setGuardBotId(UserId(update.vguard_bot_id().value_or_empty()));
using Flag = ChannelDataFlag;
const auto mask = Flag::CanSetUsername
| Flag::CanViewParticipants
diff --git a/Telegram/SourceFiles/data/data_channel.h b/Telegram/SourceFiles/data/data_channel.h
index 0325eacb98..d10b6f055c 100644
--- a/Telegram/SourceFiles/data/data_channel.h
+++ b/Telegram/SourceFiles/data/data_channel.h
@@ -351,6 +351,11 @@ public:
[[nodiscard]] bool requestToJoin() const {
return flags() & Flag::RequestToJoin;
}
+ [[nodiscard]] UserData *guardBot() const;
+ [[nodiscard]] UserId guardBotId() const {
+ return _guardBotId;
+ }
+ void setGuardBotId(UserId userId);
[[nodiscard]] bool antiSpamMode() const {
return flags() & Flag::AntiSpam;
}
@@ -615,6 +620,7 @@ private:
int _pendingRequestsCount = 0;
int _levelHint = 0;
int _starsPerMessage = 0;
+ UserId _guardBotId = 0;
Data::AllowedReactions _allowedReactions;
diff --git a/Telegram/SourceFiles/data/data_chat_participant_status.h b/Telegram/SourceFiles/data/data_chat_participant_status.h
index 013f27d4f7..294ad232c0 100644
--- a/Telegram/SourceFiles/data/data_chat_participant_status.h
+++ b/Telegram/SourceFiles/data/data_chat_participant_status.h
@@ -39,6 +39,7 @@ enum class ChatAdminRight {
DeleteStories = (1 << 16),
ManageDirect = (1 << 17),
ManageRanks = (1 << 18),
+ ProcessJoinRequests = (1 << 19),
};
inline constexpr bool is_flag_type(ChatAdminRight) { return true; }
using ChatAdminRights = base::flags;
@@ -105,6 +106,7 @@ struct AdminRightsSetOptions {
bool isGroup : 1 = false;
bool isForum : 1 = false;
bool anyoneCanAddMembers : 1 = false;
+ bool canProcessJoinRequests : 1 = false;
};
struct RestrictionsSetOptions {
diff --git a/Telegram/SourceFiles/data/data_session.cpp b/Telegram/SourceFiles/data/data_session.cpp
index 3c64b719bc..be4a09b97f 100644
--- a/Telegram/SourceFiles/data/data_session.cpp
+++ b/Telegram/SourceFiles/data/data_session.cpp
@@ -603,6 +603,7 @@ not_null Session::processUser(const MTPUser &data) {
info->userCreatesTopics = data.is_bot_forum_can_manage_topics();
info->canManageBots = data.is_bot_can_manage_bots();
info->supportsGuestChat = data.is_bot_guestchat();
+ info->supportsGuard = data.is_bot_guard();
}
}
@@ -5580,6 +5581,15 @@ auto Session::webViewResultSent() const -> rpl::producer {
return _webViewResultSent.events();
}
+void Session::joinChatWebViewDecision(JoinChatWebViewDecision &&decision) {
+ return _joinChatWebViewDecision.fire(std::move(decision));
+}
+
+auto Session::joinChatWebViewDecision() const
+-> rpl::producer {
+ return _joinChatWebViewDecision.events();
+}
+
rpl::producer> Session::peerDecorationsUpdated() const {
return _peerDecorationsUpdated.events();
}
diff --git a/Telegram/SourceFiles/data/data_session.h b/Telegram/SourceFiles/data/data_session.h
index 243427960f..a0103f29d0 100644
--- a/Telegram/SourceFiles/data/data_session.h
+++ b/Telegram/SourceFiles/data/data_session.h
@@ -959,6 +959,14 @@ public:
void webViewResultSent(WebViewResultSent &&sent);
[[nodiscard]] rpl::producer webViewResultSent() const;
+ struct JoinChatWebViewDecision {
+ PeerId peerId;
+ uint64 queryId = 0;
+ MTPJoinChatBotResult result;
+ };
+ void joinChatWebViewDecision(JoinChatWebViewDecision &&decision);
+ [[nodiscard]] rpl::producer joinChatWebViewDecision() const;
+
void saveViewAsMessages(not_null forum, bool viewAsMessages);
[[nodiscard]] auto peerDecorationsUpdated() const
@@ -1359,6 +1367,7 @@ private:
base::flat_map, MTP::DcId> _peerStatsDcIds;
rpl::event_stream _webViewResultSent;
+ rpl::event_stream _joinChatWebViewDecision;
rpl::event_stream> _peerDecorationsUpdated;
base::flat_map<
diff --git a/Telegram/SourceFiles/data/data_user.h b/Telegram/SourceFiles/data/data_user.h
index 8eb1076614..3b6f00df47 100644
--- a/Telegram/SourceFiles/data/data_user.h
+++ b/Telegram/SourceFiles/data/data_user.h
@@ -109,6 +109,7 @@ struct BotInfo {
bool setBotPhotoHidden : 1 = false;
bool canManageBots : 1 = false;
bool supportsGuestChat : 1 = false;
+ bool supportsGuard : 1 = false;
private:
std::unique_ptr _forum;
diff --git a/Telegram/SourceFiles/inline_bots/bot_attach_web_view.cpp b/Telegram/SourceFiles/inline_bots/bot_attach_web_view.cpp
index 110da84d97..197b3e04ac 100644
--- a/Telegram/SourceFiles/inline_bots/bot_attach_web_view.cpp
+++ b/Telegram/SourceFiles/inline_bots/bot_attach_web_view.cpp
@@ -208,6 +208,33 @@ constexpr auto kPopularAppBotsLimit = 100;
return result;
}
+[[nodiscard]] QString JoinChatDecisionToast(
+ not_null session,
+ PeerId peerId,
+ const MTPJoinChatBotResult &result) {
+ const auto isChannel = [&] {
+ if (const auto peer = session->data().peerLoaded(peerId)) {
+ return peer->isBroadcast();
+ }
+ return false;
+ }();
+ return result.match([&](const MTPDjoinChatBotResultApproved &) {
+ return isChannel
+ ? tr::lng_action_you_joined_by_request_channel(tr::now)
+ : tr::lng_action_you_joined_by_request(tr::now);
+ }, [&](const MTPDjoinChatBotResultDeclined &) {
+ return isChannel
+ ? tr::lng_group_request_declined_channel(tr::now)
+ : tr::lng_group_request_declined(tr::now);
+ }, [&](const MTPDjoinChatBotResultQueued &) {
+ return isChannel
+ ? tr::lng_group_request_sent_channel(tr::now)
+ : tr::lng_group_request_sent(tr::now);
+ }, [&](const MTPDjoinChatBotResultWebView &) {
+ return QString();
+ });
+}
+
[[nodiscard]] Ui::LocationPickerConfig ResolveMapsConfig(
not_null session) {
const auto &appConfig = session->appConfig();
@@ -985,6 +1012,13 @@ void WebViewInstance::resolve() {
}
}, [&](WebViewSourceAgeVerification) {
requestMain();
+ }, [&](WebViewSourceJoinChat data) {
+ confirmOpen([=] {
+ show({
+ .url = data.url,
+ .queryId = data.queryId,
+ });
+ }, true);
});
}
@@ -1386,7 +1420,7 @@ void WebViewInstance::show(ShowArgs &&args) {
not_null{ _bot },
&AttachWebViewBot::user);
const auto hasOpenBot = v::is(_source)
- || (_context.action->history->peer != _bot);
+ || (_context.action && _context.action->history->peer != _bot);
const auto hasRemoveFromMenu = (attached != end(bots))
&& (!attached->inactive || attached->inMainMenu)
&& (v::is(_source)
@@ -1450,6 +1484,18 @@ void WebViewInstance::close() {
}
void WebViewInstance::started(uint64 queryId) {
+ if (v::is(_source)) {
+ if (!queryId) {
+ return;
+ }
+ _session->attachWebView().watchJoinChatWebView(
+ queryId,
+ _parentShow,
+ _context.controller,
+ this);
+ return;
+ }
+
Expects(_context.action.has_value());
if (!queryId) {
@@ -2333,6 +2379,42 @@ AttachWebView::AttachWebView(not_null session)
, _storage(std::make_unique(session))
, _refreshTimer([=] { requestBots(); }) {
_refreshTimer.callEach(kRefreshBotsTimeout);
+ _session->data().joinChatWebViewDecision(
+ ) | rpl::on_next([=](
+ const Data::Session::JoinChatWebViewDecision &decision) {
+ const auto i = _joinChatWebViews.find(decision.queryId);
+ if (i == end(_joinChatWebViews)) {
+ return;
+ }
+ auto data = std::move(i->second);
+ _joinChatWebViews.erase(i);
+
+ const auto text = JoinChatDecisionToast(
+ _session,
+ decision.peerId,
+ decision.result);
+ const auto approved = (decision.result.type()
+ == mtpc_joinChatBotResultApproved);
+ const auto instance = data.instance.get();
+ if (instance) {
+ close(instance);
+ if (approved) {
+ if (const auto peer = _session->data().peerLoaded(
+ decision.peerId)) {
+ if (const auto window = WindowForThread(
+ data.controller,
+ peer->owner().history(peer))) {
+ window->showPeerHistory(
+ peer,
+ Window::SectionShow::Way::Forward);
+ }
+ }
+ }
+ }
+ if (data.show && !text.isEmpty()) {
+ data.show->showToast(text);
+ }
+ }, _lifetime);
}
AttachWebView::~AttachWebView() {
@@ -2385,6 +2467,18 @@ void AttachWebView::openByUsername(
}));
}
+void AttachWebView::watchJoinChatWebView(
+ uint64 queryId,
+ std::shared_ptr show,
+ base::weak_ptr controller,
+ base::weak_ptr instance) {
+ _joinChatWebViews[queryId] = {
+ .show = std::move(show),
+ .controller = controller,
+ .instance = instance,
+ };
+}
+
void AttachWebView::close(not_null instance) {
const auto i = ranges::find(
_instances,
diff --git a/Telegram/SourceFiles/inline_bots/bot_attach_web_view.h b/Telegram/SourceFiles/inline_bots/bot_attach_web_view.h
index 8d6e55839a..53cb0a6c7e 100644
--- a/Telegram/SourceFiles/inline_bots/bot_attach_web_view.h
+++ b/Telegram/SourceFiles/inline_bots/bot_attach_web_view.h
@@ -173,6 +173,15 @@ struct WebViewSourceAgeVerification {
}
};
+struct WebViewSourceJoinChat {
+ QString url;
+ uint64 queryId = 0;
+
+ friend inline bool operator==(
+ const WebViewSourceJoinChat &,
+ const WebViewSourceJoinChat &) = default;
+};
+
struct WebViewSource : std::variant<
WebViewSourceButton,
WebViewSourceSwitch,
@@ -184,7 +193,8 @@ struct WebViewSource : std::variant<
WebViewSourceBotMenu,
WebViewSourceGame,
WebViewSourceBotProfile,
- WebViewSourceAgeVerification> {
+ WebViewSourceAgeVerification,
+ WebViewSourceJoinChat> {
using variant::variant;
};
@@ -357,6 +367,11 @@ public:
const QString &botUsername,
const QString &startCommand,
bool fullscreen);
+ void watchJoinChatWebView(
+ uint64 queryId,
+ std::shared_ptr show,
+ base::weak_ptr controller,
+ base::weak_ptr instance);
void cancel();
@@ -445,11 +460,18 @@ private:
rpl::event_stream<> _attachBotsUpdates;
base::flat_set> _disclaimerAccepted;
+ struct JoinChatWebView {
+ std::shared_ptr show;
+ base::weak_ptr controller;
+ base::weak_ptr instance;
+ };
+ base::flat_map _joinChatWebViews;
std::vector> _instances;
std::vector> _popularAppBots;
mtpRequestId _popularAppBotsRequestId = 0;
rpl::variable _popularAppBotsLoaded = false;
+ rpl::lifetime _lifetime;
};
diff --git a/Telegram/SourceFiles/mtproto/scheme/api.tl b/Telegram/SourceFiles/mtproto/scheme/api.tl
index 2ad8d9c0dc..f2f907c7f0 100644
--- a/Telegram/SourceFiles/mtproto/scheme/api.tl
+++ b/Telegram/SourceFiles/mtproto/scheme/api.tl
@@ -86,7 +86,7 @@ storage.fileMp4#b3cea0e4 = storage.FileType;
storage.fileWebp#1081464c = storage.FileType;
userEmpty#d3bc4b7a id:long = User;
-user#31774388 flags:# self:flags.10?true contact:flags.11?true mutual_contact:flags.12?true deleted:flags.13?true bot:flags.14?true bot_chat_history:flags.15?true bot_nochats:flags.16?true verified:flags.17?true restricted:flags.18?true min:flags.20?true bot_inline_geo:flags.21?true support:flags.23?true scam:flags.24?true apply_min_photo:flags.25?true fake:flags.26?true bot_attach_menu:flags.27?true premium:flags.28?true attach_menu_enabled:flags.29?true flags2:# bot_can_edit:flags2.1?true close_friend:flags2.2?true stories_hidden:flags2.3?true stories_unavailable:flags2.4?true contact_require_premium:flags2.10?true bot_business:flags2.11?true bot_has_main_app:flags2.13?true bot_forum_view:flags2.16?true bot_forum_can_manage_topics:flags2.17?true bot_can_manage_bots:flags2.18?true bot_guestchat:flags2.19?true id:long access_hash:flags.0?long first_name:flags.1?string last_name:flags.2?string username:flags.3?string phone:flags.4?string photo:flags.5?UserProfilePhoto status:flags.6?UserStatus bot_info_version:flags.14?int restriction_reason:flags.18?Vector bot_inline_placeholder:flags.19?string lang_code:flags.22?string emoji_status:flags.30?EmojiStatus usernames:flags2.0?Vector stories_max_id:flags2.5?RecentStory color:flags2.8?PeerColor profile_color:flags2.9?PeerColor bot_active_users:flags2.12?int bot_verification_icon:flags2.14?long send_paid_messages_stars:flags2.15?long = User;
+user#31774388 flags:# self:flags.10?true contact:flags.11?true mutual_contact:flags.12?true deleted:flags.13?true bot:flags.14?true bot_chat_history:flags.15?true bot_nochats:flags.16?true verified:flags.17?true restricted:flags.18?true min:flags.20?true bot_inline_geo:flags.21?true support:flags.23?true scam:flags.24?true apply_min_photo:flags.25?true fake:flags.26?true bot_attach_menu:flags.27?true premium:flags.28?true attach_menu_enabled:flags.29?true flags2:# bot_can_edit:flags2.1?true close_friend:flags2.2?true stories_hidden:flags2.3?true stories_unavailable:flags2.4?true contact_require_premium:flags2.10?true bot_business:flags2.11?true bot_has_main_app:flags2.13?true bot_forum_view:flags2.16?true bot_forum_can_manage_topics:flags2.17?true bot_can_manage_bots:flags2.18?true bot_guestchat:flags2.19?true bot_guard:flags2.20?true id:long access_hash:flags.0?long first_name:flags.1?string last_name:flags.2?string username:flags.3?string phone:flags.4?string photo:flags.5?UserProfilePhoto status:flags.6?UserStatus bot_info_version:flags.14?int restriction_reason:flags.18?Vector bot_inline_placeholder:flags.19?string lang_code:flags.22?string emoji_status:flags.30?EmojiStatus usernames:flags2.0?Vector stories_max_id:flags2.5?RecentStory color:flags2.8?PeerColor profile_color:flags2.9?PeerColor bot_active_users:flags2.12?int bot_verification_icon:flags2.14?long send_paid_messages_stars:flags2.15?long = User;
userProfilePhotoEmpty#4f11bae1 = UserProfilePhoto;
userProfilePhoto#82d1f706 flags:# has_video:flags.0?true personal:flags.2?true photo_id:long stripped_thumb:flags.1?bytes dc_id:int = UserProfilePhoto;
@@ -105,7 +105,7 @@ channel#1c32b11c flags:# creator:flags.0?true left:flags.2?true broadcast:flags.
channelForbidden#17d493d5 flags:# broadcast:flags.5?true megagroup:flags.8?true monoforum:flags.10?true id:long access_hash:long title:string until_date:flags.16?int = Chat;
chatFull#2633421b flags:# can_set_username:flags.7?true has_scheduled:flags.8?true translations_disabled:flags.19?true id:long about:string participants:ChatParticipants chat_photo:flags.2?Photo notify_settings:PeerNotifySettings exported_invite:flags.13?ExportedChatInvite bot_info:flags.3?Vector pinned_msg_id:flags.6?int folder_id:flags.11?int call:flags.12?InputGroupCall ttl_period:flags.14?int groupcall_default_join_as:flags.15?Peer theme_emoticon:flags.16?string requests_pending:flags.17?int recent_requesters:flags.17?Vector available_reactions:flags.18?ChatReactions reactions_limit:flags.20?int = ChatFull;
-channelFull#e4e0b29d flags:# can_view_participants:flags.3?true can_set_username:flags.6?true can_set_stickers:flags.7?true hidden_prehistory:flags.10?true can_set_location:flags.16?true has_scheduled:flags.19?true can_view_stats:flags.20?true blocked:flags.22?true flags2:# can_delete_channel:flags2.0?true antispam:flags2.1?true participants_hidden:flags2.2?true translations_disabled:flags2.3?true stories_pinned_available:flags2.5?true view_forum_as_messages:flags2.6?true restricted_sponsored:flags2.11?true can_view_revenue:flags2.12?true paid_media_allowed:flags2.14?true can_view_stars_revenue:flags2.15?true paid_reactions_available:flags2.16?true stargifts_available:flags2.19?true paid_messages_available:flags2.20?true id:long about:string participants_count:flags.0?int admins_count:flags.1?int kicked_count:flags.2?int banned_count:flags.2?int online_count:flags.13?int read_inbox_max_id:int read_outbox_max_id:int unread_count:int chat_photo:Photo notify_settings:PeerNotifySettings exported_invite:flags.23?ExportedChatInvite bot_info:Vector migrated_from_chat_id:flags.4?long migrated_from_max_id:flags.4?int pinned_msg_id:flags.5?int stickerset:flags.8?StickerSet available_min_id:flags.9?int folder_id:flags.11?int linked_chat_id:flags.14?long location:flags.15?ChannelLocation slowmode_seconds:flags.17?int slowmode_next_send_date:flags.18?int stats_dc:flags.12?int pts:int call:flags.21?InputGroupCall ttl_period:flags.24?int pending_suggestions:flags.25?Vector groupcall_default_join_as:flags.26?Peer theme_emoticon:flags.27?string requests_pending:flags.28?int recent_requesters:flags.28?Vector default_send_as:flags.29?Peer available_reactions:flags.30?ChatReactions reactions_limit:flags2.13?int stories:flags2.4?PeerStories wallpaper:flags2.7?WallPaper boosts_applied:flags2.8?int boosts_unrestrict:flags2.9?int emojiset:flags2.10?StickerSet bot_verification:flags2.17?BotVerification stargifts_count:flags2.18?int send_paid_messages_stars:flags2.21?long main_tab:flags2.22?ProfileTab = ChatFull;
+channelFull#a04e8d3a flags:# can_view_participants:flags.3?true can_set_username:flags.6?true can_set_stickers:flags.7?true hidden_prehistory:flags.10?true can_set_location:flags.16?true has_scheduled:flags.19?true can_view_stats:flags.20?true blocked:flags.22?true flags2:# can_delete_channel:flags2.0?true antispam:flags2.1?true participants_hidden:flags2.2?true translations_disabled:flags2.3?true stories_pinned_available:flags2.5?true view_forum_as_messages:flags2.6?true restricted_sponsored:flags2.11?true can_view_revenue:flags2.12?true paid_media_allowed:flags2.14?true can_view_stars_revenue:flags2.15?true paid_reactions_available:flags2.16?true stargifts_available:flags2.19?true paid_messages_available:flags2.20?true id:long about:string participants_count:flags.0?int admins_count:flags.1?int kicked_count:flags.2?int banned_count:flags.2?int online_count:flags.13?int read_inbox_max_id:int read_outbox_max_id:int unread_count:int chat_photo:Photo notify_settings:PeerNotifySettings exported_invite:flags.23?ExportedChatInvite bot_info:Vector migrated_from_chat_id:flags.4?long migrated_from_max_id:flags.4?int pinned_msg_id:flags.5?int stickerset:flags.8?StickerSet available_min_id:flags.9?int folder_id:flags.11?int linked_chat_id:flags.14?long location:flags.15?ChannelLocation slowmode_seconds:flags.17?int slowmode_next_send_date:flags.18?int stats_dc:flags.12?int pts:int call:flags.21?InputGroupCall ttl_period:flags.24?int pending_suggestions:flags.25?Vector groupcall_default_join_as:flags.26?Peer theme_emoticon:flags.27?string requests_pending:flags.28?int recent_requesters:flags.28?Vector default_send_as:flags.29?Peer available_reactions:flags.30?ChatReactions reactions_limit:flags2.13?int stories:flags2.4?PeerStories wallpaper:flags2.7?WallPaper boosts_applied:flags2.8?int boosts_unrestrict:flags2.9?int emojiset:flags2.10?StickerSet bot_verification:flags2.17?BotVerification stargifts_count:flags2.18?int send_paid_messages_stars:flags2.21?long main_tab:flags2.22?ProfileTab guard_bot_id:flags2.23?long = ChatFull;
chatParticipant#38e79fde flags:# user_id:long inviter_id:long date:int rank:flags.0?string = ChatParticipant;
chatParticipantCreator#e1f867b8 flags:# user_id:long rank:flags.0?string = ChatParticipant;
@@ -405,7 +405,7 @@ updateBotStopped#c4870a49 user_id:long date:int stopped:Bool qts:int = Update;
updateGroupCallConnection#b783982 flags:# presentation:flags.0?true params:DataJSON = Update;
updateBotCommands#4d712f2e peer:Peer bot_id:long commands:Vector = Update;
updatePendingJoinRequests#7063c3db peer:Peer requests_pending:int recent_requesters:Vector = Update;
-updateBotChatInviteRequester#11dfa986 peer:Peer date:int user_id:long about:string invite:ExportedChatInvite qts:int = Update;
+updateBotChatInviteRequester#7cb34d79 flags:# peer:Peer date:int user_id:long about:string invite:ExportedChatInvite qts:int query_id:flags.0?long = Update;
updateMessageReactions#1e297bfa flags:# peer:Peer msg_id:int top_msg_id:flags.0?int saved_peer_id:flags.1?Peer reactions:MessageReactions = Update;
updateAttachMenuBots#17b7a20b = Update;
updateWebViewResultSent#1592b79d query_id:long = Update;
@@ -467,6 +467,10 @@ updateChatParticipantRank#bd8367b9 chat_id:long user_id:long rank:string version
updateManagedBot#4880ed9a user_id:long bot_id:long qts:int = Update;
updateBotGuestChatQuery#cdd4093d flags:# query_id:long message:Message reference_messages:flags.0?Vector qts:int = Update;
updateAiComposeTones#8c0f91fb = Update;
+updateJoinChatWebViewDecision#bdac7e70 peer:Peer query_id:long result:JoinChatBotResult = Update;
+updateNewBotConnection#b22083a6 flags:# confirmed:flags.0?true bot_id:long date:flags.1?int device:flags.1?string location:flags.1?string = Update;
+updateWebBrowserSettings#c39a2ade flags:# open_external_browser:flags.0?true display_close_button:flags.1?true = Update;
+updateWebBrowserException#140502d1 flags:# delete:flags.1?true open_external_browser:flags.0?Bool exception:WebDomainException = Update;
updates.state#a56c2a3e pts:int qts:int date:int seq:int unread_count:int = updates.State;
@@ -1828,7 +1832,7 @@ inputQuickReplyShortcutId#1190cf1 shortcut_id:int = InputQuickReplyShortcut;
messages.quickReplies#c68d6695 quick_replies:Vector messages:Vector chats:Vector users:Vector = messages.QuickReplies;
messages.quickRepliesNotModified#5f91eb5b = messages.QuickReplies;
-connectedBot#cd64636c flags:# bot_id:long recipients:BusinessBotRecipients rights:BusinessBotRights = ConnectedBot;
+connectedBot#33ed001 flags:# bot_id:long recipients:BusinessBotRecipients rights:BusinessBotRights device:flags.0?string date:flags.1?int location:flags.2?string = ConnectedBot;
account.connectedBots#17d7f87b connected_bots:Vector users:Vector = account.ConnectedBots;
@@ -2167,6 +2171,19 @@ aiComposeToneExample#f1d628ec from:TextWithEntities to:TextWithEntities = AiComp
bots.accessSettings#dd1fbf93 flags:# restricted:flags.0?true add_users:flags.1?Vector = bots.AccessSettings;
+messages.chatInviteJoinResultOk#445663a7 updates:Updates = messages.ChatInviteJoinResult;
+messages.chatInviteJoinResultWebView#774bbdf4 bot_id:long url:string query_id:long users:Vector = messages.ChatInviteJoinResult;
+
+joinChatBotResultApproved#ae152a69 = JoinChatBotResult;
+joinChatBotResultDeclined#efa0194 = JoinChatBotResult;
+joinChatBotResultQueued#98a3a840 = JoinChatBotResult;
+joinChatBotResultWebView#d6e3b813 url:string = JoinChatBotResult;
+
+webDomainException#933ca597 flags:# domain:string url:string title:string favicon:flags.0?long = WebDomainException;
+
+account.webBrowserSettingsNotModified#c31c8f4e = account.WebBrowserSettings;
+account.webBrowserSettings#79eb8cb3 flags:# open_external_browser:flags.0?true display_close_button:flags.1?true external_exceptions:Vector inapp_exceptions:Vector hash:long = account.WebBrowserSettings;
+
---functions---
invokeAfterMsg#cb9f372d {X:Type} msg_id:long query:!X = X;
@@ -2331,6 +2348,11 @@ account.initPasskeyRegistration#429547e8 = account.PasskeyRegistrationOptions;
account.registerPasskey#55b41fd6 credential:InputPasskeyCredential = Passkey;
account.getPasskeys#ea1f0c52 = account.Passkeys;
account.deletePasskey#f5b5563f id:string = Bool;
+account.confirmBotConnection#67ed1f68 bot_id:InputUser = Bool;
+account.getWebBrowserSettings#56655768 hash:long = account.WebBrowserSettings;
+account.updateWebBrowserSettings#9adf82fe flags:# open_external_browser:flags.0?true display_close_button:flags.1?true = account.WebBrowserSettings;
+account.toggleWebBrowserSettingsException#2d0a0571 flags:# delete:flags.1?true open_external_browser:flags.0?Bool url:string = Bool;
+account.deleteWebBrowserSettingsExceptions#86a0765d = account.WebBrowserSettings;
users.getUsers#d91a548 id:Vector = Vector;
users.getFullUser#b60f5918 id:InputUser = users.UserFull;
@@ -2408,7 +2430,7 @@ messages.getAllStickers#b8a0a1a8 hash:long = messages.AllStickers;
messages.getWebPagePreview#570d6f6f flags:# message:string entities:flags.3?Vector = messages.WebPagePreview;
messages.exportChatInvite#a455de90 flags:# legacy_revoke_permanent:flags.2?true request_needed:flags.3?true peer:InputPeer expire_date:flags.0?int usage_limit:flags.1?int title:flags.4?string subscription_pricing:flags.5?StarsSubscriptionPricing = ExportedChatInvite;
messages.checkChatInvite#3eadb1bb hash:string = ChatInvite;
-messages.importChatInvite#6c50051c hash:string = Updates;
+messages.importChatInvite#de91436e hash:string = messages.ChatInviteJoinResult;
messages.getStickerSet#c8a0ec74 stickerset:InputStickerSet hash:int = messages.StickerSet;
messages.installStickerSet#c78fe460 stickerset:InputStickerSet archived:Bool = messages.StickerSetInstallResult;
messages.uninstallStickerSet#f96e55de stickerset:InputStickerSet = Bool;
@@ -2684,7 +2706,7 @@ channels.editTitle#566decd0 channel:InputChannel title:string = Updates;
channels.editPhoto#f12e57c9 channel:InputChannel photo:InputChatPhoto = Updates;
channels.checkUsername#10e6bd2c channel:InputChannel username:string = Bool;
channels.updateUsername#3514b3de channel:InputChannel username:string = Bool;
-channels.joinChannel#24b524c5 channel:InputChannel = Updates;
+channels.joinChannel#7f6a1e22 channel:InputChannel = messages.ChatInviteJoinResult;
channels.leaveChannel#f836aa95 channel:InputChannel = Updates;
channels.inviteToChannel#c9e33d54 channel:InputChannel users:Vector = messages.InvitedUsers;
channels.deleteChannel#c0111fe3 channel:InputChannel = Updates;
@@ -2707,7 +2729,7 @@ channels.convertToGigagroup#b290c69 channel:InputChannel = Updates;
channels.getSendAs#e785a43f flags:# for_paid_reactions:flags.0?true for_live_stories:flags.1?true peer:InputPeer = channels.SendAsPeers;
channels.deleteParticipantHistory#367544db channel:InputChannel participant:InputPeer = messages.AffectedHistory;
channels.toggleJoinToSend#e4cb9580 channel:InputChannel enabled:Bool = Updates;
-channels.toggleJoinRequest#4c2985b6 channel:InputChannel enabled:Bool = Updates;
+channels.toggleJoinRequest#ecc2618 flags:# apply_to_invites:flags.1?true channel:InputChannel enabled:Bool guard_bot:flags.0?InputUser = Updates;
channels.reorderUsernames#b45ced1d channel:InputChannel order:Vector = Bool;
channels.toggleUsername#50f24105 channel:InputChannel username:string active:Bool = Bool;
channels.deactivateAllUsernames#a245dd3 channel:InputChannel = Bool;
@@ -2766,6 +2788,7 @@ bots.requestWebViewButton#31a2a35e user_id:InputUser button:KeyboardButton = bot
bots.getRequestedWebViewButton#bf25b7f3 bot:InputUser webapp_req_id:string = KeyboardButton;
bots.getAccessSettings#213853a3 bot:InputUser = bots.AccessSettings;
bots.editAccessSettings#31813cd8 flags:# restricted:flags.0?true bot:InputUser add_users:flags.1?Vector = Bool;
+bots.setJoinChatResults#e71a4810 query_id:long result:JoinChatBotResult = Bool;
payments.getPaymentForm#37148dbb flags:# invoice:InputInvoice theme_params:flags.0?DataJSON = payments.PaymentForm;
payments.getPaymentReceipt#2478d1cc peer:InputPeer msg_id:int = payments.PaymentReceipt;
@@ -2976,4 +2999,4 @@ aicompose.getTone#b2e8ba03 tone:InputAiComposeTone = aicompose.Tones;
aicompose.getTones#abd59201 hash:long = aicompose.Tones;
aicompose.getToneExample#d1b4ab14 tone:InputAiComposeTone num:int = AiComposeToneExample;
-// LAYER 225
+// LAYER 226
diff --git a/Telegram/SourceFiles/ui/boxes/edit_invite_link.cpp b/Telegram/SourceFiles/ui/boxes/edit_invite_link.cpp
index d72a0511bc..4d72f26f3b 100644
--- a/Telegram/SourceFiles/ui/boxes/edit_invite_link.cpp
+++ b/Telegram/SourceFiles/ui/boxes/edit_invite_link.cpp
@@ -13,6 +13,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/layers/generic_box.h"
#include "ui/vertical_list.h"
#include "ui/text/format_values.h"
+#include "ui/text/text_utilities.h"
#include "ui/widgets/checkbox.h"
#include "ui/widgets/fields/input_field.h"
#include "ui/widgets/fields/number_input.h"
@@ -43,6 +44,39 @@ constexpr auto kMaxLabelLength = 32;
}
}
+[[nodiscard]] TextWithEntities RequestApprovalAbout(
+ bool enabled,
+ bool locked,
+ bool isGroup,
+ const QString &guardBotUsername,
+ const QString &guardBotLink) {
+ if (locked) {
+ return TextWithEntities{
+ (isGroup
+ ? tr::lng_group_invite_about_approve_disabled
+ : tr::lng_group_invite_about_approve_disabled_channel)(
+ tr::now)
+ };
+ } else if (enabled && !guardBotUsername.isEmpty()) {
+ return (isGroup
+ ? tr::lng_group_invite_about_approve_managed
+ : tr::lng_group_invite_about_approve_managed_channel)(
+ tr::now,
+ lt_bot,
+ tr::link('@' + guardBotUsername, guardBotLink),
+ tr::marked);
+ }
+ return TextWithEntities{
+ enabled
+ ? (isGroup
+ ? tr::lng_group_invite_about_approve
+ : tr::lng_group_invite_about_approve_channel)(tr::now)
+ : (isGroup
+ ? tr::lng_group_invite_about_no_approve
+ : tr::lng_group_invite_about_no_approve_channel)(tr::now)
+ };
+}
+
} // namespace
void EditInviteLinkBox(
@@ -55,7 +89,11 @@ void EditInviteLinkBox(
const auto link = data.link;
const auto isGroup = data.isGroup;
const auto isPublic = data.isPublic;
+ const auto globalRequestApproval = data.globalRequestApproval;
+ const auto guardBotUsername = data.guardBotUsername;
+ const auto guardBotLink = data.guardBotLink;
const auto subscriptionLocked = data.subscriptionCredits > 0;
+ const auto requestApprovalLocked = isPublic && !globalRequestApproval;
box->setTitle(link.isEmpty()
? tr::lng_group_invite_new_title()
: tr::lng_group_invite_edit_title());
@@ -86,6 +124,20 @@ void EditInviteLinkBox(
st::defaultBoxDividerLabelPadding),
margins);
};
+ const auto addRichDivider = [&](
+ not_null container,
+ rpl::producer text,
+ style::margins margins = style::margins()) {
+ container->add(
+ object_ptr(
+ container,
+ object_ptr(
+ container,
+ std::move(text),
+ st::boxDividerLabel),
+ st::defaultBoxDividerLabelPadding),
+ margins);
+ };
const auto now = base::unixtime::now();
const auto expire = data.expireDate ? data.expireDate : kMaxLimit;
@@ -105,33 +157,10 @@ void EditInviteLinkBox(
const auto state = box->lifetime().make_state(State{
.expireValue = expire,
.usageValue = usage,
- .requestApproval = (data.requestApproval && !isPublic),
+ .requestApproval = data.requestApproval,
.subscription = false,
});
- const auto requestApproval = (isPublic || subscriptionLocked)
- ? nullptr
- : container->add(
- object_ptr(
- container,
- tr::lng_group_invite_request_approve(),
- st::settingsButtonNoIcon),
- style::margins{ 0, 0, 0, st::defaultVerticalListSkip });
- if (requestApproval) {
- requestApproval->toggleOn(state->requestApproval.value(), true);
- requestApproval->setClickedCallback([=] {
- state->requestApproval.force_assign(!requestApproval->toggled());
- state->subscription.force_assign(false);
- });
- addDivider(container, rpl::conditional(
- state->requestApproval.value(),
- (isGroup
- ? tr::lng_group_invite_about_approve()
- : tr::lng_group_invite_about_approve_channel()),
- (isGroup
- ? tr::lng_group_invite_about_no_approve()
- : tr::lng_group_invite_about_no_approve_channel())));
- }
auto credits = (Ui::NumberInput*)(nullptr);
if (!isPublic && fillSubscription) {
Ui::AddSkip(container);
@@ -157,49 +186,57 @@ void EditInviteLinkBox(
});
}
- const auto labelField = container->add(
- object_ptr(
- container,
- st::defaultInputField,
- tr::lng_group_invite_label_header(),
- data.label),
- style::margins(
- st::defaultSubsectionTitlePadding.left(),
- st::defaultVerticalListSkip,
- st::defaultSubsectionTitlePadding.right(),
- st::defaultVerticalListSkip * 2));
- labelField->setMaxLength(kMaxLabelLength);
- addDivider(container, tr::lng_group_invite_label_about());
-
- const auto &saveLabel = link.isEmpty()
- ? tr::lng_formatting_link_create
- : tr::lng_settings_save;
- box->addButton(saveLabel(), [=] {
- const auto label = labelField->getLastText();
- const auto expireDate = (state->expireValue == kMaxLimit)
- ? 0
- : (state->expireValue < 0)
- ? (base::unixtime::now() - state->expireValue)
- : state->expireValue;
- const auto usageLimit = (state->usageValue == kMaxLimit)
- ? 0
- : state->usageValue;
- done(InviteLinkFields{
- .link = link,
- .label = label,
- .expireDate = expireDate,
- .usageLimit = usageLimit,
- .subscriptionCredits = credits
- ? credits->getLastText().toInt()
- : 0,
- .requestApproval = state->requestApproval.current(),
- .isGroup = isGroup,
- .isPublic = isPublic,
+ const auto addLabelField = [=] {
+ const auto result = container->add(
+ object_ptr(
+ container,
+ st::defaultInputField,
+ tr::lng_group_invite_label_header(),
+ data.label),
+ style::margins(
+ st::defaultSubsectionTitlePadding.left(),
+ st::defaultVerticalListSkip,
+ st::defaultSubsectionTitlePadding.right(),
+ st::defaultVerticalListSkip * 2));
+ result->setMaxLength(kMaxLabelLength);
+ addDivider(container, tr::lng_group_invite_label_about());
+ return result;
+ };
+ const auto addSaveButtons = [=](not_null labelField) {
+ const auto &saveLabel = link.isEmpty()
+ ? tr::lng_formatting_link_create
+ : tr::lng_settings_save;
+ box->addButton(saveLabel(), [=] {
+ const auto label = labelField->getLastText();
+ const auto expireDate = (state->expireValue == kMaxLimit)
+ ? 0
+ : (state->expireValue < 0)
+ ? (base::unixtime::now() - state->expireValue)
+ : state->expireValue;
+ const auto usageLimit = (state->usageValue == kMaxLimit)
+ ? 0
+ : state->usageValue;
+ done(InviteLinkFields{
+ .link = link,
+ .label = label,
+ .expireDate = expireDate,
+ .usageLimit = usageLimit,
+ .subscriptionCredits = credits
+ ? credits->getLastText().toInt()
+ : 0,
+ .requestApproval = state->requestApproval.current(),
+ .isGroup = isGroup,
+ .isPublic = isPublic,
+ .globalRequestApproval = globalRequestApproval,
+ .guardBotUsername = guardBotUsername,
+ .guardBotLink = guardBotLink,
+ });
});
- });
- box->addButton(tr::lng_cancel(), [=] { box->closeBox(); });
+ box->addButton(tr::lng_cancel(), [=] { box->closeBox(); });
+ };
if (subscriptionLocked) {
+ addSaveButtons(addLabelField());
return;
}
addTitle(container, tr::lng_group_invite_expire_title());
@@ -373,6 +410,39 @@ void EditInviteLinkBox(
usagesSlide->toggleOn(state->requestApproval.value() | rpl::map(!_1));
usagesSlide->finishAnimating();
+
+ const auto skip = st::defaultVerticalListSkip;
+ const auto requestApproval = container->add(
+ object_ptr(
+ container,
+ (isGroup
+ ? tr::lng_group_invite_request_approve
+ : tr::lng_group_invite_request_approve_channel)(),
+ st::settingsButtonNoIcon),
+ style::margins{ 0, skip, 0, skip });
+ requestApproval->toggleOn(state->requestApproval.value(), true);
+ requestApproval->setToggleLocked(requestApprovalLocked);
+ requestApproval->finishAnimating();
+ requestApproval->setClickedCallback([=] {
+ if (requestApprovalLocked) {
+ return;
+ }
+ state->requestApproval.force_assign(!requestApproval->toggled());
+ state->subscription.force_assign(false);
+ });
+ addRichDivider(
+ container,
+ state->requestApproval.value(
+ ) | rpl::map([=](bool enabled) {
+ return RequestApprovalAbout(
+ enabled,
+ requestApprovalLocked,
+ isGroup,
+ guardBotUsername,
+ guardBotLink);
+ }));
+
+ addSaveButtons(addLabelField());
}
void CreateInviteLinkBox(
@@ -380,11 +450,20 @@ void CreateInviteLinkBox(
Fn fillSubscription,
bool isGroup,
bool isPublic,
+ bool globalRequestApproval,
+ const QString &guardBotUsername,
+ const QString &guardBotLink,
Fn done) {
EditInviteLinkBox(
box,
std::move(fillSubscription),
- InviteLinkFields{ .isGroup = isGroup, .isPublic = isPublic },
+ InviteLinkFields{
+ .isGroup = isGroup,
+ .isPublic = isPublic,
+ .globalRequestApproval = globalRequestApproval,
+ .guardBotUsername = guardBotUsername,
+ .guardBotLink = guardBotLink,
+ },
std::move(done));
}
diff --git a/Telegram/SourceFiles/ui/boxes/edit_invite_link.h b/Telegram/SourceFiles/ui/boxes/edit_invite_link.h
index 0b9805cd69..dfe35f5e1f 100644
--- a/Telegram/SourceFiles/ui/boxes/edit_invite_link.h
+++ b/Telegram/SourceFiles/ui/boxes/edit_invite_link.h
@@ -22,6 +22,9 @@ struct InviteLinkFields {
bool requestApproval = false;
bool isGroup = false;
bool isPublic = false;
+ bool globalRequestApproval = false;
+ QString guardBotUsername;
+ QString guardBotLink;
};
struct InviteLinkSubscriptionToggle final {
@@ -40,6 +43,9 @@ void CreateInviteLinkBox(
Fn fillSubscription,
bool isGroup,
bool isPublic,
+ bool globalRequestApproval,
+ const QString &guardBotUsername,
+ const QString &guardBotLink,
Fn done);
} // namespace Ui
diff --git a/Telegram/SourceFiles/ui/effects/premium.style b/Telegram/SourceFiles/ui/effects/premium.style
index 794359f9ca..f5267ad4cd 100644
--- a/Telegram/SourceFiles/ui/effects/premium.style
+++ b/Telegram/SourceFiles/ui/effects/premium.style
@@ -343,6 +343,7 @@ boostReplaceUserpic: UserpicButton(defaultUserpicButton) {
photoSize: 60px;
}
boostReplaceIcon: icon{{ "stories/boost_mini", premiumButtonFg }};
+guardBotReplaceIcon: icon{{ "chat/mini_shield_lock", premiumButtonFg }};
boostReplaceIconSkip: 3px;
boostReplaceIconOutline: 2px;
boostReplaceIconAdd: point(4px, 2px);