From ccb8c43961cfecbf69c439ae89af656a4be682b3 Mon Sep 17 00:00:00 2001 From: John Preston Date: Mon, 21 Jul 2025 16:47:41 +0400 Subject: [PATCH] Support age verification for sensitive media. --- .../icons/settings/filled_verify_age.svg | 12 + Telegram/Resources/langs/cloud_lang.strings | 3 + Telegram/Resources/langs/lang.strings | 7 + .../SourceFiles/api/api_sensitive_content.cpp | 24 +- .../SourceFiles/api/api_sensitive_content.h | 4 + Telegram/SourceFiles/data/data_session.cpp | 3 + .../view/media/history_view_media_common.cpp | 336 +++++++++++++++--- .../inline_bots/bot_attach_web_view.cpp | 8 + .../inline_bots/bot_attach_web_view.h | 14 +- Telegram/SourceFiles/lang/lang_tag.cpp | 13 +- Telegram/SourceFiles/lang/lang_tag.h | 1 + Telegram/SourceFiles/main/main_app_config.cpp | 16 + Telegram/SourceFiles/main/main_app_config.h | 5 + Telegram/SourceFiles/settings/settings.style | 15 + .../ui/chat/attach/attach_bot_webview.cpp | 10 + .../ui/chat/attach/attach_bot_webview.h | 1 + 16 files changed, 416 insertions(+), 56 deletions(-) create mode 100644 Telegram/Resources/icons/settings/filled_verify_age.svg diff --git a/Telegram/Resources/icons/settings/filled_verify_age.svg b/Telegram/Resources/icons/settings/filled_verify_age.svg new file mode 100644 index 0000000000..2b13be56ca --- /dev/null +++ b/Telegram/Resources/icons/settings/filled_verify_age.svg @@ -0,0 +1,12 @@ + + + Filled / filled_verify_age + + + + + + + + + \ No newline at end of file diff --git a/Telegram/Resources/langs/cloud_lang.strings b/Telegram/Resources/langs/cloud_lang.strings index c7e33531c0..3776c023a4 100644 --- a/Telegram/Resources/langs/cloud_lang.strings +++ b/Telegram/Resources/langs/cloud_lang.strings @@ -13,6 +13,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "cloud_lng_topup_purpose_subs" = "Buy **Stars** to keep your channel subscriptions."; +"cloud_lng_age_verify_about_gb#one" = "To access such content, you must confirm that you are at least **{count}** year old as required by UK law."; +"cloud_lng_age_verify_about_gb#other" = "To access such content, you must confirm that you are at least **{count}** years old as required by UK law."; + "cloud_lng_passport_in_ar" = "Arabic"; "cloud_lng_passport_in_az" = "Azerbaijani"; "cloud_lng_passport_in_bg" = "Bulgarian"; diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index cfeb51a1eb..0b988fb211 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -6756,6 +6756,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_frozen_text3" = "Appeal via {link} before {date}, or your account will be deleted."; "lng_frozen_appeal_button" = "Submit an Appeal"; +"lng_age_verify_title" = "Age Verification"; +"lng_age_verify_mobile" = "Please open this media in the official Telegram app for Android or iOS to verify your age."; +"lng_age_verify_here" = "This is a one-time process using your phone's camera. Your selfie will not be stored by Telegram."; +"lng_age_verify_button" = "Verify My Age"; +"lng_age_verify_sorry_title" = "Age Check Failed"; +"lng_age_verify_sorry_text" = "Sorry, you can't view 18+ content."; + "lng_context_bank_card_copy" = "Copy Card Number"; "lng_context_bank_card_copied" = "Card number copied to clipboard."; diff --git a/Telegram/SourceFiles/api/api_sensitive_content.cpp b/Telegram/SourceFiles/api/api_sensitive_content.cpp index 31f83f5eba..e1e25950e8 100644 --- a/Telegram/SourceFiles/api/api_sensitive_content.cpp +++ b/Telegram/SourceFiles/api/api_sensitive_content.cpp @@ -25,7 +25,7 @@ SensitiveContent::SensitiveContent(not_null api) } void SensitiveContent::preload() { - if (!_loaded) { + if (!_loaded && !_loadRequestId) { reload(); } } @@ -37,7 +37,6 @@ void SensitiveContent::reload(bool force) { } return; } - _loaded = true; _loadRequestId = _api.request(MTPaccount_GetContentSettings( )).done([=](const MTPaccount_ContentSettings &result) { _loadRequestId = 0; @@ -50,6 +49,10 @@ void SensitiveContent::reload(bool force) { _enabled = enabled; _canChange = canChange; } + if (!_loaded) { + _loaded = true; + _loadedChanged.fire({}); + } if (base::take(_appConfigReloadForce) || changed) { _appConfigReloadTimer.callOnce(kRefreshAppConfigTimeout); } @@ -61,6 +64,19 @@ void SensitiveContent::reload(bool force) { }).send(); } +bool SensitiveContent::loaded() const { + return _loaded; +} + +rpl::producer SensitiveContent::loadedValue() const { + if (_loaded) { + return rpl::single(true); + } + return rpl::single(false) | rpl::then( + _loadedChanged.events() | rpl::map_to(true) + ); +} + bool SensitiveContent::enabledCurrent() const { return _enabled.current(); } @@ -69,6 +85,10 @@ rpl::producer SensitiveContent::enabled() const { return _enabled.value(); } +bool SensitiveContent::canChangeCurrent() const { + return _canChange.current(); +} + rpl::producer SensitiveContent::canChange() const { return _canChange.value(); } diff --git a/Telegram/SourceFiles/api/api_sensitive_content.h b/Telegram/SourceFiles/api/api_sensitive_content.h index 576bf275f3..e35f8cbf46 100644 --- a/Telegram/SourceFiles/api/api_sensitive_content.h +++ b/Telegram/SourceFiles/api/api_sensitive_content.h @@ -26,12 +26,16 @@ public: void reload(bool force = false); void update(bool enabled); + [[nodiscard]] bool loaded() const; + [[nodiscard]] rpl::producer loadedValue() const; [[nodiscard]] bool enabledCurrent() const; [[nodiscard]] rpl::producer enabled() const; + [[nodiscard]] bool canChangeCurrent() const; [[nodiscard]] rpl::producer canChange() const; private: const not_null _session; + rpl::event_stream<> _loadedChanged; MTP::Sender _api; mtpRequestId _loadRequestId = 0; mtpRequestId _saveRequestId = 0; diff --git a/Telegram/SourceFiles/data/data_session.cpp b/Telegram/SourceFiles/data/data_session.cpp index ec2569bf55..f3c306a350 100644 --- a/Telegram/SourceFiles/data/data_session.cpp +++ b/Telegram/SourceFiles/data/data_session.cpp @@ -1399,6 +1399,9 @@ UserData *Session::userByPhone(const QString &phone) const { PeerData *Session::peerByUsername(const QString &username) const { const auto uname = username.trimmed(); + if (uname.isEmpty()) { + return nullptr; + } for (const auto &[peerId, peer] : _peers) { if (peer->isLoaded() && !peer->username().compare(uname, Qt::CaseInsensitive)) { diff --git a/Telegram/SourceFiles/history/view/media/history_view_media_common.cpp b/Telegram/SourceFiles/history/view/media/history_view_media_common.cpp index b899d9fc98..b1e9eeb3ad 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_media_common.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_media_common.cpp @@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "api/api_sensitive_content.h" #include "api/api_views.h" #include "apiwrap.h" +#include "inline_bots/bot_attach_web_view.h" #include "ui/boxes/confirm_box.h" #include "ui/layers/generic_box.h" #include "ui/text/format_values.h" @@ -18,6 +19,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/widgets/checkbox.h" #include "ui/wrap/slide_wrap.h" #include "ui/painter.h" +#include "core/application.h" #include "core/click_handler_types.h" #include "data/data_document.h" #include "data/data_session.h" @@ -34,18 +36,77 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/history_item.h" #include "history/history.h" #include "lang/lang_keys.h" +#include "lottie/lottie_icon.h" +#include "main/main_app_config.h" #include "main/main_session.h" #include "mainwindow.h" #include "media/streaming/media_streaming_utility.h" #include "payments/payments_checkout_process.h" #include "payments/payments_non_panel_process.h" +#include "settings/settings_common.h" +#include "webrtc/webrtc_environment.h" +#include "webview/webview_interface.h" #include "window/window_session_controller.h" #include "styles/style_chat.h" +#include "styles/style_layers.h" +#include "styles/style_settings.h" namespace HistoryView { namespace { constexpr auto kMediaUnlockedTooltipDuration = 5 * crl::time(1000); +const auto kVerifyAgeAboutPrefix = "cloud_lng_age_verify_about_"; + +rpl::producer AgeVerifyAbout( + not_null session) { + const auto appConfig = &session->appConfig(); + return rpl::single( + rpl::empty + ) | rpl::then( + Lang::Updated() + ) | rpl::map([=] { + const auto country = appConfig->ageVerifyCountry().toLower(); + const auto age = appConfig->ageVerifyMinAge(); + const auto [shift, string] = Lang::Plural( + Lang::kPluralKeyBaseForCloudValue, + age, + lt_count); + const auto postfixes = { + "#zero", + "#one", + "#two", + "#few", + "#many", + "#other" + }; + Assert(shift >= 0 && shift < postfixes.size()); + const auto postfix = *(begin(postfixes) + shift); + return Ui::Text::RichLangValue(u"To access such content, you must confirm that you are at least **{count}** years old as required by UK law."_q.replace(u"{count}"_q, string)); + return Ui::Text::RichLangValue(Lang::GetNonDefaultValue( + kVerifyAgeAboutPrefix + country.toUtf8() + postfix AssertIsDebug(test_this) + ).replace(u"{count}"_q, string)); + }); +} + +[[nodiscard]] object_ptr AgeVerifyIcon( + not_null parent) { + const auto padding = st::settingsAgeVerifyIconPadding; + const auto full = st::settingsAgeVerifyIcon.size().grownBy(padding); + auto result = object_ptr(parent); + const auto raw = result.data(); + raw->resize(full); + raw->paintRequest() | rpl::start_with_next([=] { + auto p = QPainter(raw); + const auto x = (raw->width() - full.width()) / 2; + auto hq = PainterHighQualityEnabler(p); + p.setBrush(st::windowBgActive); + p.setPen(Qt::NoPen); + const auto inner = QRect(QPoint(x, 0), full); + p.drawEllipse(inner); + st::settingsAgeVerifyIcon.paintInCenter(p, inner); + }, raw->lifetime()); + return result; +} } // namespace @@ -278,61 +339,246 @@ ClickHandlerPtr MakePaidMediaLink(not_null item) { }); } +void ShowAgeVerification( + std::shared_ptr show, + not_null bot, + Fn reveal) { + show->show(Box([=](not_null box) { + box->setNoContentMargin(true); + box->setStyle(st::settingsAgeVerifyBox); + box->setWidth(st::boxWideWidth); + + box->addRow(AgeVerifyIcon(box), st::settingsAgeVerifyIconMargin); + + box->addRow( + object_ptr( + box, + tr::lng_age_verify_title(), + st::settingsAgeVerifyTitle), + st::boxRowPadding + st::settingsAgeVerifyMargin); + box->addRow( + object_ptr( + box, + AgeVerifyAbout(&bot->session()), + st::settingsAgeVerifyText), + st::boxRowPadding + st::settingsAgeVerifyMargin); + box->addRow( + object_ptr( + box, + tr::lng_age_verify_here(Ui::Text::RichLangValue), + st::settingsAgeVerifyText), + st::boxRowPadding + st::settingsAgeVerifyMargin); + + const auto weak = QPointer(box); + const auto done = crl::guard(&bot->session(), [=](int age) { + const auto min = bot->session().appConfig().ageVerifyMinAge(); + if (age >= min) { + reveal(); + bot->session().api().sensitiveContent().update(true); + } else { + show->showToast({ + .title = tr::lng_age_verify_sorry_title(tr::now), + .text = tr::lng_age_verify_sorry_text(tr::now), + .duration = Ui::Toast::kDefaultDuration * 3, + }); + } + if (const auto strong = weak.data()) { + strong->closeBox(); + } + }); + const auto button = box->addButton(tr::lng_age_verify_button(), [=] { + bot->session().attachWebView().open({ + .bot = bot, + .context = { .maySkipConfirmation = true }, + .source = InlineBots::WebViewSourceAgeVerification{ + .done = done, + }, + }); + }); + box->widthValue( + ) | rpl::start_with_next([=](int width) { + const auto &padding = st::settingsAgeVerifyBox.buttonPadding; + button->resizeToWidth(width + - padding.left() + - padding.right()); + button->moveToLeft(padding.left(), padding.top()); + }, button->lifetime()); + })); +} + +void ShowAgeVerificationMobile( + std::shared_ptr show, + not_null session) { + show->show(Box([=](not_null box) { + box->setTitle(tr::lng_age_verify_title()); + box->setWidth(st::boxWideWidth); + + const auto size = st::settingsCloudPasswordIconSize; + auto icon = Settings::CreateLottieIcon( + box->verticalLayout(), + { + .name = u"phone"_q, + .sizeOverride = { size, size }, + }, + st::peerAppearanceIconPadding); + + box->showFinishes( + ) | rpl::start_with_next([animate = std::move(icon.animate)] { + animate(anim::repeat::once); + }, box->lifetime()); + + box->addRow(std::move(icon.widget)); + + box->addRow( + object_ptr( + box, + AgeVerifyAbout(session), + st::settingsAgeVerifyText), + st::boxRowPadding + st::settingsAgeVerifyMargin); + box->addRow( + object_ptr( + box, + tr::lng_age_verify_mobile(Ui::Text::RichLangValue), + st::settingsAgeVerifyText), + st::boxRowPadding + st::settingsAgeVerifyMargin); + + box->addButton(tr::lng_box_ok(), [=] { + box->closeBox(); + }); + })); +} + +void ShowAgeVerificationRequired( + std::shared_ptr show, + not_null session, + Fn reveal) { + struct State { + Fn check; + rpl::lifetime lifetime; + std::optional bot; + }; + const auto state = std::make_shared(); + const auto username = session->appConfig().ageVerifyBotUsername(); + const auto bot = session->data().peerByUsername(username); + if (username.isEmpty() || bot) { + state->bot = bot; + } else { + session->api().request(MTPcontacts_ResolveUsername( + MTP_flags(0), + MTP_string(username), + MTPstring() + )).done([=](const MTPcontacts_ResolvedPeer &result) { + const auto &data = result.data(); + session->data().processUsers(data.vusers()); + session->data().processChats(data.vchats()); + const auto botId = peerFromMTP(data.vpeer()); + state->bot = session->data().peerLoaded(botId); + state->check(); + }).fail([=] { + state->bot = nullptr; + state->check(); + }).send(); + } + state->check = [=] { + const auto sensitive = &session->api().sensitiveContent(); + const auto ready = sensitive->loaded(); + if (!ready) { + state->lifetime = sensitive->loadedValue( + ) | rpl::filter( + rpl::mappers::_1 + ) | rpl::take(1) | rpl::start_with_next(state->check); + return; + } else if (!state->bot.has_value()) { + return; + } + const auto has = Core::App().mediaDevices().recordAvailability(); + const auto available = Webview::Availability(); + const auto bot = (*state->bot)->asUser(); + if (available.error == Webview::Available::Error::None + && has == Webrtc::RecordAvailability::VideoAndAudio + && sensitive->canChangeCurrent() + && bot + && bot->isBot() + && bot->botInfo->hasMainApp) { + ShowAgeVerification(show, bot, reveal); + } else { + ShowAgeVerificationMobile(show, session); + } + state->lifetime.destroy(); + state->check = nullptr; + }; + state->check(); +} + +void ShowSensitiveConfirm( + std::shared_ptr show, + not_null session, + Fn reveal) { + show->show(Box([=](not_null box) { + struct State { + rpl::variable canChange; + Ui::Checkbox *checkbox = nullptr; + }; + const auto state = box->lifetime().make_state(); + const auto sensitive = &session->api().sensitiveContent(); + state->canChange = sensitive->canChange(); + const auto done = [=](Fn close) { + if (state->canChange.current() + && state->checkbox->checked()) { + show->showToast({ + .text = tr::lng_sensitive_toast( + tr::now, + Ui::Text::RichLangValue), + .adaptive = true, + .duration = 5 * crl::time(1000), + }); + sensitive->update(true); + } else { + reveal(); + } + close(); + }; + Ui::ConfirmBox(box, { + .text = tr::lng_sensitive_text(Ui::Text::RichLangValue), + .confirmed = done, + .confirmText = tr::lng_sensitive_view(), + .title = tr::lng_sensitive_title(), + }); + const auto skip = st::defaultCheckbox.margin.bottom(); + const auto wrap = box->addRow( + object_ptr>( + box, + object_ptr( + box, + tr::lng_sensitive_always(tr::now), + false)), + st::boxRowPadding + QMargins(0, 0, 0, skip)); + wrap->toggleOn(state->canChange.value()); + wrap->finishAnimating(); + state->checkbox = wrap->entity(); + })); +} + ClickHandlerPtr MakeSensitiveMediaLink( ClickHandlerPtr reveal, not_null item) { const auto session = &item->history()->session(); + session->api().sensitiveContent().preload(); + return std::make_shared([=](ClickContext context) { + const auto plain = [reveal, context] { + reveal->onClick(context); + }; const auto my = context.other.value(); const auto controller = my.sessionWindow.get(); const auto show = controller ? controller->uiShow() : my.show; if (!show) { - reveal->onClick(context); - return; + plain(); + } else if (session->appConfig().ageVerifyNeeded()) { + ShowAgeVerificationRequired(show, session, plain); + } else { + ShowSensitiveConfirm(show, session, plain); } - show->show(Box([=](not_null box) { - struct State { - rpl::variable canChange; - Ui::Checkbox *checkbox = nullptr; - }; - const auto state = box->lifetime().make_state(); - const auto sensitive = &session->api().sensitiveContent(); - state->canChange = sensitive->canChange(); - const auto done = [=](Fn close) { - if (state->canChange.current() - && state->checkbox->checked()) { - show->showToast({ - .text = tr::lng_sensitive_toast( - tr::now, - Ui::Text::RichLangValue), - .adaptive = true, - .duration = 5 * crl::time(1000), - }); - sensitive->update(true); - } else { - reveal->onClick(context); - } - close(); - }; - Ui::ConfirmBox(box, { - .text = tr::lng_sensitive_text(Ui::Text::RichLangValue), - .confirmed = done, - .confirmText = tr::lng_sensitive_view(), - .title = tr::lng_sensitive_title(), - }); - const auto skip = st::defaultCheckbox.margin.bottom(); - const auto wrap = box->addRow( - object_ptr>( - box, - object_ptr( - box, - tr::lng_sensitive_always(tr::now), - false)), - st::boxRowPadding + QMargins(0, 0, 0, skip)); - wrap->toggleOn(state->canChange.value()); - wrap->finishAnimating(); - state->checkbox = wrap->entity(); - })); }); } diff --git a/Telegram/SourceFiles/inline_bots/bot_attach_web_view.cpp b/Telegram/SourceFiles/inline_bots/bot_attach_web_view.cpp index 70901ecd7f..36e907923f 100644 --- a/Telegram/SourceFiles/inline_bots/bot_attach_web_view.cpp +++ b/Telegram/SourceFiles/inline_bots/bot_attach_web_view.cpp @@ -931,6 +931,8 @@ void WebViewInstance::resolve() { requestMain(); }); } + }, [&](WebViewSourceAgeVerification) { + requestMain(); }); } @@ -1997,6 +1999,12 @@ void WebViewInstance::botDownloadFile( }).send(); } +void WebViewInstance::botVerifyAge(int age) { + if (v::is(_source)) { + v::get(_source).done(age); + } +} + void WebViewInstance::botOpenPrivacyPolicy() { const auto bot = _bot; const auto weak = _context.controller; diff --git a/Telegram/SourceFiles/inline_bots/bot_attach_web_view.h b/Telegram/SourceFiles/inline_bots/bot_attach_web_view.h index 442cc3fc78..e7fe7f281e 100644 --- a/Telegram/SourceFiles/inline_bots/bot_attach_web_view.h +++ b/Telegram/SourceFiles/inline_bots/bot_attach_web_view.h @@ -163,6 +163,16 @@ struct WebViewSourceBotProfile { WebViewSourceBotProfile) = default; }; +struct WebViewSourceAgeVerification { + Fn done; + + friend inline bool operator==( + WebViewSourceAgeVerification, + WebViewSourceAgeVerification) { + return true; + } +}; + struct WebViewSource : std::variant< WebViewSourceButton, WebViewSourceSwitch, @@ -173,7 +183,8 @@ struct WebViewSource : std::variant< WebViewSourceAttachMenu, WebViewSourceBotMenu, WebViewSourceGame, - WebViewSourceBotProfile> { + WebViewSourceBotProfile, + WebViewSourceAgeVerification> { using variant::variant; }; @@ -291,6 +302,7 @@ private: Ui::BotWebView::SetEmojiStatusRequest request) override; void botDownloadFile( Ui::BotWebView::DownloadFileRequest request) override; + void botVerifyAge(int age) override; void botOpenPrivacyPolicy() override; void botClose() override; diff --git a/Telegram/SourceFiles/lang/lang_tag.cpp b/Telegram/SourceFiles/lang/lang_tag.cpp index 02620f2e1d..5136a67a5e 100644 --- a/Telegram/SourceFiles/lang/lang_tag.cpp +++ b/Telegram/SourceFiles/lang/lang_tag.cpp @@ -1004,14 +1004,11 @@ PluralResult Plural( const auto t = f; const auto useNonDefaultPlural = (ChoosePlural != ChoosePluralDefault) - && Lang::details::IsNonDefaultPlural(keyBase); - const auto shift = (useNonDefaultPlural ? ChoosePlural : ChoosePluralDefault)( - (integer ? i : -1), - i, - v, - w, - f, - t); + && (keyBase == kPluralKeyBaseForCloudValue + || Lang::details::IsNonDefaultPlural(keyBase)); + const auto shift = (useNonDefaultPlural + ? ChoosePlural + : ChoosePluralDefault)((integer ? i : -1), i, v, w, f, t); if (integer) { const auto round = qRound(value); if (type == lt_count_short) { diff --git a/Telegram/SourceFiles/lang/lang_tag.h b/Telegram/SourceFiles/lang/lang_tag.h index 40f3ef9972..3d0f231186 100644 --- a/Telegram/SourceFiles/lang/lang_tag.h +++ b/Telegram/SourceFiles/lang/lang_tag.h @@ -38,6 +38,7 @@ struct PluralResult { int keyShift = 0; QString replacement; }; +inline constexpr auto kPluralKeyBaseForCloudValue = ushort(-1); PluralResult Plural( ushort keyBase, float64 value, diff --git a/Telegram/SourceFiles/main/main_app_config.cpp b/Telegram/SourceFiles/main/main_app_config.cpp index b65c104555..ea8561669e 100644 --- a/Telegram/SourceFiles/main/main_app_config.cpp +++ b/Telegram/SourceFiles/main/main_app_config.cpp @@ -208,6 +208,22 @@ TimeId AppConfig::suggestedPostAgeMin() const { return get(u"stars_suggested_post_age_min"_q, 86400); } +bool AppConfig::ageVerifyNeeded() const { + return get(u"need_age_video_verification"_q, false); +} + +QString AppConfig::ageVerifyCountry() const { + return get(u"verify_age_country"_q, QString()); +} + +int AppConfig::ageVerifyMinAge() const { + return get(u"verify_age_min"_q, 18); +} + +QString AppConfig::ageVerifyBotUsername() const { + return get(u"verify_age_bot_username"_q, QString()); +} + void AppConfig::refresh(bool force) { if (_requestId || !_api) { if (force) { diff --git a/Telegram/SourceFiles/main/main_app_config.h b/Telegram/SourceFiles/main/main_app_config.h index a2ea65f426..c5ba22ee40 100644 --- a/Telegram/SourceFiles/main/main_app_config.h +++ b/Telegram/SourceFiles/main/main_app_config.h @@ -102,6 +102,11 @@ public: [[nodiscard]] int suggestedPostDelayMax() const; [[nodiscard]] TimeId suggestedPostAgeMin() const; + [[nodiscard]] bool ageVerifyNeeded() const; + [[nodiscard]] QString ageVerifyCountry() const; + [[nodiscard]] int ageVerifyMinAge() const; + [[nodiscard]] QString ageVerifyBotUsername() const; + void refresh(bool force = false); private: diff --git a/Telegram/SourceFiles/settings/settings.style b/Telegram/SourceFiles/settings/settings.style index b153dbe371..70dded5edc 100644 --- a/Telegram/SourceFiles/settings/settings.style +++ b/Telegram/SourceFiles/settings/settings.style @@ -705,3 +705,18 @@ settingsCreditsButton: SettingsButton(settingsButton) { } settingsButtonIconGift: icon {{ "settings/gift", menuIconColor }}; settingsButtonIconEarn: icon {{ "settings/earn", menuIconColor }}; + +settingsAgeVerifyBox: Box(filterInviteBox) { + buttonPadding: margins(24px, 24px, 24px, 24px); +} +settingsAgeVerifyText: FlatLabel(defaultFlatLabel) { + minWidth: 200px; + align: align(top); +} +settingsAgeVerifyMargin: margins(0px, 6px, 0px, 6px); +settingsAgeVerifyTitle: FlatLabel(boxTitle) { + align: align(top); +} +settingsAgeVerifyIcon: icon {{ "settings/filled_verify_age-48x48", windowFgActive }}; +settingsAgeVerifyIconPadding: margins(16px, 16px, 16px, 16px); +settingsAgeVerifyIconMargin: margins(0px, 24px, 0px, 14px); diff --git a/Telegram/SourceFiles/ui/chat/attach/attach_bot_webview.cpp b/Telegram/SourceFiles/ui/chat/attach/attach_bot_webview.cpp index 526dceceed..525bc4eb05 100644 --- a/Telegram/SourceFiles/ui/chat/attach/attach_bot_webview.cpp +++ b/Telegram/SourceFiles/ui/chat/attach/attach_bot_webview.cpp @@ -1011,6 +1011,16 @@ bool Panel::createWebview(const Webview::ThemeParams ¶ms) { secureStorageFailed(arguments); } else if (command == "web_app_secure_storage_clear") { secureStorageFailed(arguments); + } else if (command == "web_app_verify_age") { + const auto passed = arguments["passed"]; + const auto detected = arguments["age"]; + const auto valid = passed.isBool() + && passed.toBool() + && detected.isDouble(); + const auto age = valid + ? int(std::floor(detected.toDouble())) + : 0; + _delegate->botVerifyAge(age); } else if (command == "share_score") { _delegate->botHandleMenuButton(MenuButton::ShareGame); } diff --git a/Telegram/SourceFiles/ui/chat/attach/attach_bot_webview.h b/Telegram/SourceFiles/ui/chat/attach/attach_bot_webview.h index 72edb0b391..63b11fc624 100644 --- a/Telegram/SourceFiles/ui/chat/attach/attach_bot_webview.h +++ b/Telegram/SourceFiles/ui/chat/attach/attach_bot_webview.h @@ -105,6 +105,7 @@ public: virtual void botDownloadFile(DownloadFileRequest request) = 0; virtual void botSendPreparedMessage( SendPreparedMessageRequest request) = 0; + virtual void botVerifyAge(int age) = 0; virtual void botOpenPrivacyPolicy() = 0; virtual void botClose() = 0; };