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 @@
+
+
\ 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;
};