Added ability to add proxy from clipboard.

This commit is contained in:
23rd 2024-05-28 18:46:04 +03:00 committed by John Preston
parent 48e3802565
commit f6b849e4f7
3 changed files with 123 additions and 28 deletions

View file

@ -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.";

View file

@ -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<QString, QString> &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<base::unique_qptr<Ui::PopupMenu>>();
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<Ui::PopupMenu>(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<Ui::VerticalLayout>(this));
@ -1096,24 +1188,13 @@ ProxiesBoxController::ProxiesBoxController(not_null<Main::Account*> account)
void ProxiesBoxController::ShowApplyConfirmation(
Type type,
const QMap<QString, QString> &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<Ui::BoxContent> 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);

View file

@ -77,6 +77,9 @@ public:
void setTryIPv6(bool enabled);
rpl::producer<ProxyData::Settings> proxySettingsValue() const;
[[nodiscard]] bool contains(const ProxyData &proxy) const;
void addNewItem(const ProxyData &proxy);
rpl::producer<ItemView> views() const;
~ProxiesBoxController();
@ -109,7 +112,6 @@ private:
void replaceItemValue(
std::vector<Item>::iterator which,
const ProxyData &proxy);
void addNewItem(const ProxyData &proxy);
const not_null<Main::Account*> _account;
Core::SettingsProxy &_settings;