2016-04-12 21:31:28 +00:00
|
|
|
/*
|
|
|
|
This file is part of Telegram Desktop,
|
2018-01-03 10:23:14 +00:00
|
|
|
the official desktop application for the Telegram messaging service.
|
2016-04-12 21:31:28 +00:00
|
|
|
|
2018-01-03 10:23:14 +00:00
|
|
|
For license and copyright information please follow this link:
|
|
|
|
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
2016-04-12 21:31:28 +00:00
|
|
|
*/
|
|
|
|
#include "core/click_handler_types.h"
|
|
|
|
|
2017-04-13 08:27:10 +00:00
|
|
|
#include "lang/lang_keys.h"
|
2019-01-21 13:42:21 +00:00
|
|
|
#include "core/application.h"
|
|
|
|
#include "core/local_url_handlers.h"
|
|
|
|
#include "core/file_utilities.h"
|
2018-11-26 11:55:02 +00:00
|
|
|
#include "mainwidget.h"
|
2019-01-18 12:27:37 +00:00
|
|
|
#include "auth_session.h"
|
2017-03-04 10:23:56 +00:00
|
|
|
#include "platform/platform_specific.h"
|
2018-01-11 19:33:26 +00:00
|
|
|
#include "history/view/history_view_element.h"
|
|
|
|
#include "history/history_item.h"
|
2017-04-06 14:38:10 +00:00
|
|
|
#include "boxes/confirm_box.h"
|
|
|
|
#include "base/qthelp_regex.h"
|
|
|
|
#include "base/qthelp_url.h"
|
2017-03-04 10:23:56 +00:00
|
|
|
#include "storage/localstorage.h"
|
2016-10-26 16:43:13 +00:00
|
|
|
#include "ui/widgets/tooltip.h"
|
2019-01-04 11:09:48 +00:00
|
|
|
#include "data/data_user.h"
|
2019-01-18 12:27:37 +00:00
|
|
|
#include "data/data_session.h"
|
2016-04-12 21:31:28 +00:00
|
|
|
|
|
|
|
namespace {
|
|
|
|
|
2016-07-28 17:01:08 +00:00
|
|
|
QString tryConvertUrlToLocal(QString url) {
|
2018-08-14 06:49:14 +00:00
|
|
|
if (url.size() > 8192) {
|
|
|
|
url = url.mid(0, 8192);
|
|
|
|
}
|
2016-07-28 17:01:08 +00:00
|
|
|
|
|
|
|
using namespace qthelp;
|
|
|
|
auto matchOptions = RegExOption::CaseInsensitive;
|
2017-05-20 15:32:24 +00:00
|
|
|
auto telegramMeMatch = regex_match(qsl("^https?://(www\\.)?(telegram\\.(me|dog)|t\\.me)/(.+)$"), url, matchOptions);
|
2016-12-20 13:03:51 +00:00
|
|
|
if (telegramMeMatch) {
|
2017-03-15 14:38:05 +00:00
|
|
|
auto query = telegramMeMatch->capturedRef(4);
|
2016-07-28 17:01:08 +00:00
|
|
|
if (auto joinChatMatch = regex_match(qsl("^joinchat/([a-zA-Z0-9\\.\\_\\-]+)(\\?|$)"), query, matchOptions)) {
|
|
|
|
return qsl("tg://join?invite=") + url_encode(joinChatMatch->captured(1));
|
|
|
|
} else if (auto stickerSetMatch = regex_match(qsl("^addstickers/([a-zA-Z0-9\\.\\_]+)(\\?|$)"), query, matchOptions)) {
|
|
|
|
return qsl("tg://addstickers?set=") + url_encode(stickerSetMatch->captured(1));
|
2018-10-22 11:13:48 +00:00
|
|
|
} else if (auto languageMatch = regex_match(qsl("^setlanguage/([a-zA-Z0-9\\.\\_\\-]+)(\\?|$)"), query, matchOptions)) {
|
|
|
|
return qsl("tg://setlanguage?lang=") + url_encode(languageMatch->captured(1));
|
2016-07-28 17:01:08 +00:00
|
|
|
} else if (auto shareUrlMatch = regex_match(qsl("^share/url/?\\?(.+)$"), query, matchOptions)) {
|
|
|
|
return qsl("tg://msg_url?") + shareUrlMatch->captured(1);
|
|
|
|
} else if (auto confirmPhoneMatch = regex_match(qsl("^confirmphone/?\\?(.+)"), query, matchOptions)) {
|
|
|
|
return qsl("tg://confirmphone?") + confirmPhoneMatch->captured(1);
|
2019-01-17 08:18:23 +00:00
|
|
|
} else if (auto ivMatch = regex_match(qsl("^iv/?\\?(.+)(#|$)"), query, matchOptions)) {
|
2018-03-04 12:52:00 +00:00
|
|
|
//
|
|
|
|
// We need to show our t.me page, not the url directly.
|
|
|
|
//
|
|
|
|
//auto params = url_parse_params(ivMatch->captured(1), UrlParamNameTransform::ToLower);
|
|
|
|
//auto previewedUrl = params.value(qsl("url"));
|
|
|
|
//if (previewedUrl.startsWith(qstr("http://"), Qt::CaseInsensitive)
|
|
|
|
// || previewedUrl.startsWith(qstr("https://"), Qt::CaseInsensitive)) {
|
|
|
|
// return previewedUrl;
|
|
|
|
//}
|
|
|
|
return url;
|
2019-01-17 08:18:23 +00:00
|
|
|
} else if (auto socksMatch = regex_match(qsl("^socks/?\\?(.+)(#|$)"), query, matchOptions)) {
|
2017-06-27 20:11:38 +00:00
|
|
|
return qsl("tg://socks?") + socksMatch->captured(1);
|
2019-01-17 08:18:23 +00:00
|
|
|
} else if (auto proxyMatch = regex_match(qsl("^proxy/?\\?(.+)(#|$)"), query, matchOptions)) {
|
2018-04-27 16:16:50 +00:00
|
|
|
return qsl("tg://proxy?") + proxyMatch->captured(1);
|
2019-01-17 08:18:23 +00:00
|
|
|
} else if (auto bgMatch = regex_match(qsl("^bg/([a-zA-Z0-9\\.\\_\\-]+)(\\?(.+)?)?$"), query, matchOptions)) {
|
|
|
|
const auto params = bgMatch->captured(3);
|
|
|
|
return qsl("tg://bg?slug=") + bgMatch->captured(1) + (params.isEmpty() ? QString() : '&' + params);
|
2019-04-04 09:33:41 +00:00
|
|
|
} else if (auto postMatch = regex_match(qsl("^c/(\\-?\\d+)/(\\d+)(#|$)"), query, matchOptions)) {
|
|
|
|
return qsl("tg://privatepost?channel=%1&post=%2").arg(postMatch->captured(1)).arg(postMatch->captured(2));
|
2016-07-28 17:01:08 +00:00
|
|
|
} else if (auto usernameMatch = regex_match(qsl("^([a-zA-Z0-9\\.\\_]+)(/?\\?|/?$|/(\\d+)/?(?:\\?|$))"), query, matchOptions)) {
|
2017-05-13 12:14:34 +00:00
|
|
|
auto params = query.mid(usernameMatch->captured(0).size()).toString();
|
|
|
|
auto postParam = QString();
|
2016-07-28 17:01:08 +00:00
|
|
|
if (auto postMatch = regex_match(qsl("^/\\d+/?(?:\\?|$)"), usernameMatch->captured(2))) {
|
|
|
|
postParam = qsl("&post=") + usernameMatch->captured(3);
|
|
|
|
}
|
|
|
|
return qsl("tg://resolve/?domain=") + url_encode(usernameMatch->captured(1)) + postParam + (params.isEmpty() ? QString() : '&' + params);
|
2016-04-12 21:31:28 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return url;
|
|
|
|
}
|
|
|
|
|
2017-07-15 12:41:15 +00:00
|
|
|
bool UrlRequiresConfirmation(const QUrl &url) {
|
|
|
|
using namespace qthelp;
|
|
|
|
return !regex_match(qsl("(^|\\.)(telegram\\.org|telegra\\.ph|telesco\\.pe)$"), url.host(), RegExOption::CaseInsensitive);
|
|
|
|
}
|
|
|
|
|
2016-04-12 21:31:28 +00:00
|
|
|
} // namespace
|
|
|
|
|
2018-02-06 07:24:55 +00:00
|
|
|
UrlClickHandler::UrlClickHandler(const QString &url, bool fullDisplayed)
|
|
|
|
: TextClickHandler(fullDisplayed)
|
|
|
|
, _originalUrl(url) {
|
|
|
|
if (isEmail()) {
|
|
|
|
_readable = _originalUrl;
|
|
|
|
} else {
|
|
|
|
const auto original = QUrl(_originalUrl);
|
|
|
|
const auto good = QUrl(original.isValid()
|
|
|
|
? original.toEncoded()
|
|
|
|
: QString());
|
|
|
|
_readable = good.isValid() ? good.toDisplayString() : _originalUrl;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-07-18 15:15:02 +00:00
|
|
|
QString UrlClickHandler::copyToClipboardContextItemText() const {
|
2019-06-19 15:09:03 +00:00
|
|
|
return isEmail()
|
|
|
|
? tr::lng_context_copy_email(tr::now)
|
|
|
|
: tr::lng_context_copy_link(tr::now);
|
2017-07-18 15:15:02 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
QString UrlClickHandler::url() const {
|
|
|
|
if (isEmail()) {
|
|
|
|
return _originalUrl;
|
|
|
|
}
|
|
|
|
|
|
|
|
QUrl u(_originalUrl), good(u.isValid() ? u.toEncoded() : QString());
|
|
|
|
QString result(good.isValid() ? QString::fromUtf8(good.toEncoded()) : _originalUrl);
|
|
|
|
|
|
|
|
if (!result.isEmpty() && !QRegularExpression(qsl("^[a-zA-Z]+:")).match(result).hasMatch()) { // no protocol
|
|
|
|
return qsl("http://") + result;
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2018-07-09 18:13:48 +00:00
|
|
|
void UrlClickHandler::Open(QString url, QVariant context) {
|
2018-08-14 06:49:14 +00:00
|
|
|
url = tryConvertUrlToLocal(url);
|
2019-01-21 13:42:21 +00:00
|
|
|
if (Core::InternalPassportLink(url)) {
|
2016-04-12 21:31:28 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2018-08-14 06:49:14 +00:00
|
|
|
Ui::Tooltip::Hide();
|
|
|
|
if (isEmail(url)) {
|
|
|
|
File::OpenEmailLink(url);
|
|
|
|
} else if (url.startsWith(qstr("tg://"), Qt::CaseInsensitive)) {
|
2019-01-21 13:42:21 +00:00
|
|
|
Core::App().openLocalUrl(url, context);
|
2018-08-14 06:49:14 +00:00
|
|
|
} else if (!url.isEmpty()) {
|
2016-04-12 21:31:28 +00:00
|
|
|
QDesktopServices::openUrl(url);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-04-08 15:10:06 +00:00
|
|
|
auto UrlClickHandler::getTextEntity() const -> TextEntity {
|
|
|
|
const auto type = isEmail(_originalUrl)
|
|
|
|
? EntityType::Email
|
|
|
|
: EntityType::Url;
|
|
|
|
return { type, _originalUrl };
|
2016-04-29 12:00:48 +00:00
|
|
|
}
|
|
|
|
|
2018-07-09 18:13:48 +00:00
|
|
|
void HiddenUrlClickHandler::Open(QString url, QVariant context) {
|
2018-08-14 06:49:14 +00:00
|
|
|
url = tryConvertUrlToLocal(url);
|
2019-01-21 13:42:21 +00:00
|
|
|
if (Core::InternalPassportLink(url)) {
|
2018-08-14 06:49:14 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2018-07-31 17:56:54 +00:00
|
|
|
const auto open = [=] {
|
2018-08-14 06:49:14 +00:00
|
|
|
UrlClickHandler::Open(url, context);
|
2018-07-31 17:56:54 +00:00
|
|
|
};
|
2018-08-14 06:49:14 +00:00
|
|
|
if (url.startsWith(qstr("tg://"), Qt::CaseInsensitive)) {
|
|
|
|
open();
|
2016-04-12 21:31:28 +00:00
|
|
|
} else {
|
2018-08-14 06:49:14 +00:00
|
|
|
const auto parsedUrl = QUrl::fromUserInput(url);
|
2019-05-25 14:34:04 +00:00
|
|
|
if (UrlRequiresConfirmation(url)
|
|
|
|
&& QGuiApplication::keyboardModifiers() != Qt::ControlModifier) {
|
2019-01-21 13:42:21 +00:00
|
|
|
Core::App().hideMediaView();
|
2018-08-14 06:49:14 +00:00
|
|
|
const auto displayUrl = parsedUrl.isValid()
|
2018-07-09 18:13:48 +00:00
|
|
|
? parsedUrl.toDisplayString()
|
2018-08-14 06:49:14 +00:00
|
|
|
: url;
|
2018-07-09 18:13:48 +00:00
|
|
|
Ui::show(
|
|
|
|
Box<ConfirmBox>(
|
2019-06-19 15:09:03 +00:00
|
|
|
tr::lng_open_this_link(tr::now) + qsl("\n\n") + displayUrl,
|
|
|
|
tr::lng_open_link(tr::now),
|
2018-07-09 18:13:48 +00:00
|
|
|
[=] { Ui::hideLayer(); open(); }),
|
|
|
|
LayerOption::KeepOther);
|
2017-07-15 12:41:15 +00:00
|
|
|
} else {
|
2018-07-09 18:13:48 +00:00
|
|
|
open();
|
2017-07-15 12:41:15 +00:00
|
|
|
}
|
2016-04-12 21:31:28 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-07-09 18:13:48 +00:00
|
|
|
void BotGameUrlClickHandler::onClick(ClickContext context) const {
|
2018-08-14 06:49:14 +00:00
|
|
|
const auto url = tryConvertUrlToLocal(this->url());
|
2019-01-21 13:42:21 +00:00
|
|
|
if (Core::InternalPassportLink(url)) {
|
2018-08-14 06:49:14 +00:00
|
|
|
return;
|
|
|
|
}
|
2016-09-15 19:15:49 +00:00
|
|
|
|
2018-07-09 18:13:48 +00:00
|
|
|
const auto open = [=] {
|
2018-08-14 06:49:14 +00:00
|
|
|
UrlClickHandler::Open(url, context.other);
|
2018-07-09 18:13:48 +00:00
|
|
|
};
|
2018-08-14 06:49:14 +00:00
|
|
|
if (url.startsWith(qstr("tg://"), Qt::CaseInsensitive)) {
|
|
|
|
open();
|
2016-09-29 16:15:44 +00:00
|
|
|
} else if (!_bot || _bot->isVerified() || Local::isBotTrusted(_bot)) {
|
2018-07-09 18:13:48 +00:00
|
|
|
open();
|
2016-09-15 19:15:49 +00:00
|
|
|
} else {
|
2018-07-09 18:13:48 +00:00
|
|
|
const auto callback = [=, bot = _bot] {
|
2016-12-13 17:07:56 +00:00
|
|
|
Ui::hideLayer();
|
|
|
|
Local::makeBotTrusted(bot);
|
2018-07-09 18:13:48 +00:00
|
|
|
open();
|
|
|
|
};
|
|
|
|
Ui::show(Box<ConfirmBox>(
|
2019-06-19 16:39:25 +00:00
|
|
|
tr::lng_allow_bot_pass(tr::now, lt_bot_name, _bot->name),
|
2019-06-19 15:09:03 +00:00
|
|
|
tr::lng_allow_bot(tr::now),
|
2018-07-09 18:13:48 +00:00
|
|
|
callback));
|
2016-09-15 19:15:49 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-04-08 15:10:06 +00:00
|
|
|
auto HiddenUrlClickHandler::getTextEntity() const -> TextEntity {
|
|
|
|
return { EntityType::CustomUrl, url() };
|
2016-04-12 21:31:28 +00:00
|
|
|
}
|
|
|
|
|
2016-04-29 12:00:48 +00:00
|
|
|
QString MentionClickHandler::copyToClipboardContextItemText() const {
|
2019-06-19 15:09:03 +00:00
|
|
|
return tr::lng_context_copy_mention(tr::now);
|
2016-04-12 21:31:28 +00:00
|
|
|
}
|
|
|
|
|
2018-07-09 18:13:48 +00:00
|
|
|
void MentionClickHandler::onClick(ClickContext context) const {
|
|
|
|
const auto button = context.button;
|
2016-04-12 21:31:28 +00:00
|
|
|
if (button == Qt::LeftButton || button == Qt::MiddleButton) {
|
2018-11-26 11:55:02 +00:00
|
|
|
App::main()->openPeerByName(_tag.mid(1), ShowAtProfileMsgId);
|
2016-04-12 21:31:28 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-04-08 15:10:06 +00:00
|
|
|
auto MentionClickHandler::getTextEntity() const -> TextEntity {
|
|
|
|
return { EntityType::Mention };
|
2016-04-29 12:00:48 +00:00
|
|
|
}
|
|
|
|
|
2018-07-09 18:13:48 +00:00
|
|
|
void MentionNameClickHandler::onClick(ClickContext context) const {
|
|
|
|
const auto button = context.button;
|
2016-04-30 17:04:14 +00:00
|
|
|
if (button == Qt::LeftButton || button == Qt::MiddleButton) {
|
2019-01-18 12:27:37 +00:00
|
|
|
if (auto user = Auth().data().userLoaded(_userId)) {
|
2016-04-30 17:04:14 +00:00
|
|
|
Ui::showPeerProfile(user);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-04-08 15:10:06 +00:00
|
|
|
auto MentionNameClickHandler::getTextEntity() const -> TextEntity {
|
2016-04-30 17:04:14 +00:00
|
|
|
auto data = QString::number(_userId) + '.' + QString::number(_accessHash);
|
2019-04-08 15:10:06 +00:00
|
|
|
return { EntityType::MentionName, data };
|
2016-04-30 17:04:14 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
QString MentionNameClickHandler::tooltip() const {
|
2019-01-18 12:27:37 +00:00
|
|
|
if (auto user = Auth().data().userLoaded(_userId)) {
|
2016-04-30 17:04:14 +00:00
|
|
|
auto name = App::peerName(user);
|
|
|
|
if (name != _text) {
|
|
|
|
return name;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return QString();
|
|
|
|
}
|
|
|
|
|
2016-04-29 12:00:48 +00:00
|
|
|
QString HashtagClickHandler::copyToClipboardContextItemText() const {
|
2019-06-19 15:09:03 +00:00
|
|
|
return tr::lng_context_copy_hashtag(tr::now);
|
2016-04-12 21:31:28 +00:00
|
|
|
}
|
|
|
|
|
2018-07-09 18:13:48 +00:00
|
|
|
void HashtagClickHandler::onClick(ClickContext context) const {
|
|
|
|
const auto button = context.button;
|
2016-04-12 21:31:28 +00:00
|
|
|
if (button == Qt::LeftButton || button == Qt::MiddleButton) {
|
|
|
|
App::searchByHashtag(_tag, Ui::getPeerForMouseAction());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-04-08 15:10:06 +00:00
|
|
|
auto HashtagClickHandler::getTextEntity() const -> TextEntity {
|
|
|
|
return { EntityType::Hashtag };
|
2016-04-29 12:00:48 +00:00
|
|
|
}
|
|
|
|
|
2018-02-06 07:24:55 +00:00
|
|
|
QString CashtagClickHandler::copyToClipboardContextItemText() const {
|
2019-06-19 15:09:03 +00:00
|
|
|
return tr::lng_context_copy_hashtag(tr::now);
|
2018-02-06 07:24:55 +00:00
|
|
|
}
|
|
|
|
|
2018-07-09 18:13:48 +00:00
|
|
|
void CashtagClickHandler::onClick(ClickContext context) const {
|
|
|
|
const auto button = context.button;
|
2018-02-06 07:24:55 +00:00
|
|
|
if (button == Qt::LeftButton || button == Qt::MiddleButton) {
|
|
|
|
App::searchByHashtag(_tag, Ui::getPeerForMouseAction());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-04-08 15:10:06 +00:00
|
|
|
auto CashtagClickHandler::getTextEntity() const -> TextEntity {
|
|
|
|
return { EntityType::Cashtag };
|
2018-02-06 07:24:55 +00:00
|
|
|
}
|
|
|
|
|
2016-05-31 19:27:11 +00:00
|
|
|
PeerData *BotCommandClickHandler::_peer = nullptr;
|
|
|
|
UserData *BotCommandClickHandler::_bot = nullptr;
|
2018-07-09 18:13:48 +00:00
|
|
|
void BotCommandClickHandler::onClick(ClickContext context) const {
|
|
|
|
const auto button = context.button;
|
2016-04-12 21:31:28 +00:00
|
|
|
if (button == Qt::LeftButton || button == Qt::MiddleButton) {
|
2016-05-31 19:27:11 +00:00
|
|
|
if (auto peer = peerForCommand()) {
|
|
|
|
if (auto bot = peer->isUser() ? peer->asUser() : botForCommand()) {
|
|
|
|
Ui::showPeerHistory(peer, ShowAtTheEndMsgId);
|
|
|
|
App::sendBotCommand(peer, bot, _cmd);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (auto peer = Ui::getPeerForMouseAction()) { // old way
|
2018-01-11 13:07:29 +00:00
|
|
|
auto bot = peer->isUser() ? peer->asUser() : nullptr;
|
|
|
|
if (!bot) {
|
|
|
|
if (const auto view = App::hoveredLinkItem()) {
|
|
|
|
// may return nullptr
|
|
|
|
bot = view->data()->fromOriginal()->asUser();
|
2016-04-14 19:24:42 +00:00
|
|
|
}
|
|
|
|
}
|
2016-04-12 21:31:28 +00:00
|
|
|
Ui::showPeerHistory(peer, ShowAtTheEndMsgId);
|
2016-04-14 19:24:42 +00:00
|
|
|
App::sendBotCommand(peer, bot, _cmd);
|
2016-04-12 21:31:28 +00:00
|
|
|
} else {
|
|
|
|
App::insertBotCommand(_cmd);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2016-04-29 12:00:48 +00:00
|
|
|
|
2019-04-08 15:10:06 +00:00
|
|
|
auto BotCommandClickHandler::getTextEntity() const -> TextEntity {
|
|
|
|
return { EntityType::BotCommand };
|
2016-04-29 12:00:48 +00:00
|
|
|
}
|