diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 848496650..87df781f8 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -1053,6 +1053,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_proxy_sponsor" = "Proxy sponsor"; "lng_proxy_sponsor_about" = "This channel is shown by your proxy server.\nTo remove this channel from your chats list,\ndisable the proxy in Telegram Settings."; "lng_proxy_sponsor_warning" = "This proxy may display a sponsored channel in your chat list. This doesn't reveal any of your Telegram traffic."; +"lng_proxy_add_from_clipboard" = "Add proxy from clipboard"; +"lng_proxy_add_from_clipboard_good_toast" = "Proxy was added from clipboard."; +"lng_proxy_add_from_clipboard_failed_toast" = "This is not a proxy link."; +"lng_proxy_add_from_clipboard_existing_toast" = "This proxy is already in the list."; "lng_badge_psa_default" = "PSA"; "lng_about_psa_default" = "This message provides you with a public service announcement. To remove it from your chats list, right click it and select **Hide**."; "lng_tooltip_psa_default" = "This message provides you with a public service announcement."; diff --git a/Telegram/SourceFiles/boxes/connection_box.cpp b/Telegram/SourceFiles/boxes/connection_box.cpp index cf9d91f8c..f50ef4b3a 100644 --- a/Telegram/SourceFiles/boxes/connection_box.cpp +++ b/Telegram/SourceFiles/boxes/connection_box.cpp @@ -7,31 +7,35 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "boxes/connection_box.h" -#include "ui/boxes/confirm_box.h" -#include "lang/lang_keys.h" -#include "storage/localstorage.h" -#include "base/qthelp_url.h" #include "base/call_delayed.h" +#include "base/qthelp_regex.h" +#include "base/qthelp_url.h" #include "core/application.h" +#include "core/click_handler_types.h" #include "core/core_settings.h" +#include "core/local_url_handlers.h" +#include "lang/lang_keys.h" #include "main/main_account.h" #include "mtproto/facade.h" -#include "ui/widgets/checkbox.h" +#include "storage/localstorage.h" +#include "ui/basic_click_handlers.h" +#include "ui/boxes/confirm_box.h" +#include "ui/effects/animations.h" +#include "ui/effects/radial_animation.h" +#include "ui/painter.h" +#include "ui/text/text_options.h" +#include "ui/text/text_utilities.h" +#include "ui/toast/toast.h" #include "ui/widgets/buttons.h" +#include "ui/widgets/checkbox.h" +#include "ui/widgets/dropdown_menu.h" #include "ui/widgets/fields/input_field.h" #include "ui/widgets/fields/number_input.h" #include "ui/widgets/fields/password_input.h" #include "ui/widgets/labels.h" -#include "ui/widgets/dropdown_menu.h" +#include "ui/widgets/popup_menu.h" #include "ui/wrap/slide_wrap.h" #include "ui/wrap/vertical_layout.h" -#include "ui/toast/toast.h" -#include "ui/effects/animations.h" -#include "ui/effects/radial_animation.h" -#include "ui/text/text_options.h" -#include "ui/text/text_utilities.h" -#include "ui/basic_click_handlers.h" -#include "ui/painter.h" #include "boxes/abstract_box.h" // Ui::show(). #include "styles/style_layers.h" #include "styles/style_boxes.h" @@ -48,6 +52,22 @@ constexpr auto kSaveSettingsDelayedTimeout = crl::time(1000); using ProxyData = MTP::ProxyData; +[[nodiscard]] ProxyData ProxyDataFromFields( + ProxyData::Type type, + const QMap &fields) { + auto proxy = ProxyData(); + proxy.type = type; + proxy.host = fields.value(u"server"_q); + proxy.port = fields.value(u"port"_q).toUInt(); + if (type == ProxyData::Type::Socks5) { + proxy.user = fields.value(u"user"_q); + proxy.password = fields.value(u"pass"_q); + } else if (type == ProxyData::Type::Mtproto) { + proxy.password = fields.value(u"secret"_q); + } + return proxy; +}; + class HostInput : public Ui::MaskedInputField { public: HostInput( @@ -203,6 +223,7 @@ protected: private: void setupContent(); + void setupTopButton(); void createNoRowsLabel(); void addNewProxy(); void applyView(View &&view); @@ -600,9 +621,80 @@ void ProxiesBox::prepare() { addButton(tr::lng_proxy_add(), [=] { addNewProxy(); }); addButton(tr::lng_close(), [=] { closeBox(); }); + setupTopButton(); setupContent(); } +void ProxiesBox::setupTopButton() { + const auto top = addTopButton(st::infoTopBarMenu); + const auto menu + = top->lifetime().make_state>(); + const auto callback = [=] { + const auto maybeUrl = QGuiApplication::clipboard()->text(); + const auto local = Core::TryConvertUrlToLocal(maybeUrl); + + const auto proxyString = u"proxy"_q; + const auto socksString = u"socks"_q; + const auto protocol = u"tg://"_q; + const auto command = base::StringViewMid( + local, + protocol.size(), + 8192); + + if (local.startsWith(protocol + proxyString) + || local.startsWith(protocol + socksString)) { + + using namespace qthelp; + const auto options = RegExOption::CaseInsensitive; + for (const auto &[expression, _] : Core::LocalUrlHandlers()) { + const auto midExpression = base::StringViewMid( + expression, + 1); + const auto isSocks = midExpression.startsWith( + socksString); + if (!midExpression.startsWith(proxyString) + && !isSocks) { + continue; + } + const auto match = regex_match( + expression, + command, + options); + if (!match) { + continue; + } + const auto type = isSocks + ? ProxyData::Type::Socks5 + : ProxyData::Type::Mtproto; + const auto fields = url_parse_params( + match->captured(1), + qthelp::UrlParamNameTransform::ToLower); + const auto proxy = ProxyDataFromFields(type, fields); + const auto contains = _controller->contains(proxy); + const auto toast = (contains + ? tr::lng_proxy_add_from_clipboard_existing_toast + : tr::lng_proxy_add_from_clipboard_good_toast)(tr::now); + uiShow()->showToast(toast); + if (!contains) { + _controller->addNewItem(proxy); + } + break; + } + } else { + uiShow()->showToast( + tr::lng_proxy_add_from_clipboard_failed_toast(tr::now)); + } + }; + top->setClickedCallback([=] { + *menu = base::make_unique_q(top, st::defaultPopupMenu); + (*menu)->addAction( + tr::lng_proxy_add_from_clipboard(tr::now), + callback); + (*menu)->popup(QCursor::pos()); + return true; + }); +} + void ProxiesBox::setupContent() { const auto inner = setInnerWidget(object_ptr(this)); @@ -1096,24 +1188,13 @@ ProxiesBoxController::ProxiesBoxController(not_null account) void ProxiesBoxController::ShowApplyConfirmation( Type type, const QMap &fields) { - const auto server = fields.value(u"server"_q); - const auto port = fields.value(u"port"_q).toUInt(); - auto proxy = ProxyData(); - proxy.type = type; - proxy.host = server; - proxy.port = port; - if (type == Type::Socks5) { - proxy.user = fields.value(u"user"_q); - proxy.password = fields.value(u"pass"_q); - } else if (type == Type::Mtproto) { - proxy.password = fields.value(u"secret"_q); - } + const auto proxy = ProxyDataFromFields(type, fields); if (proxy) { static const auto UrlStartRegExp = QRegularExpression( "^https://", QRegularExpression::CaseInsensitiveOption); static const auto UrlEndRegExp = QRegularExpression("/$"); - const auto displayed = "https://" + server + "/"; + const auto displayed = "https://" + proxy.host + "/"; const auto parsed = QUrl::fromUserInput(displayed); const auto displayUrl = !UrlClickHandler::IsSuspicious(displayed) ? displayed @@ -1131,7 +1212,7 @@ void ProxiesBoxController::ShowApplyConfirmation( lt_server, displayServer, lt_port, - QString::number(port)) + QString::number(proxy.port)) + (proxy.type == Type::Mtproto ? "\n\n" + tr::lng_proxy_sponsor_warning(tr::now) : QString()); @@ -1448,6 +1529,14 @@ object_ptr ProxiesBoxController::addNewItemBox() { }); } +bool ProxiesBoxController::contains(const ProxyData &proxy) const { + const auto j = ranges::find( + _list, + proxy, + [](const Item &item) { return item.data; }); + return (j != end(_list)); +} + void ProxiesBoxController::addNewItem(const ProxyData &proxy) { auto &proxies = _settings.list(); proxies.push_back(proxy); diff --git a/Telegram/SourceFiles/boxes/connection_box.h b/Telegram/SourceFiles/boxes/connection_box.h index 8c78ebf34..e3bbfd006 100644 --- a/Telegram/SourceFiles/boxes/connection_box.h +++ b/Telegram/SourceFiles/boxes/connection_box.h @@ -77,6 +77,9 @@ public: void setTryIPv6(bool enabled); rpl::producer proxySettingsValue() const; + [[nodiscard]] bool contains(const ProxyData &proxy) const; + void addNewItem(const ProxyData &proxy); + rpl::producer views() const; ~ProxiesBoxController(); @@ -109,7 +112,6 @@ private: void replaceItemValue( std::vector::iterator which, const ProxyData &proxy); - void addNewItem(const ProxyData &proxy); const not_null _account; Core::SettingsProxy &_settings;