| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853 |
- /*
- This file is part of Telegram Desktop,
- the official desktop application for the Telegram messaging service.
- For license and copyright information please follow this link:
- https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
- */
- #include "core/local_url_handlers.h"
- #include "api/api_authorizations.h"
- #include "api/api_confirm_phone.h"
- #include "api/api_chat_filters.h"
- #include "api/api_chat_invite.h"
- #include "api/api_premium.h"
- #include "base/qthelp_regex.h"
- #include "base/qthelp_url.h"
- #include "lang/lang_cloud_manager.h"
- #include "lang/lang_keys.h"
- #include "core/update_checker.h"
- #include "core/application.h"
- #include "core/click_handler_types.h"
- #include "dialogs/ui/dialogs_suggestions.h"
- #include "boxes/background_preview_box.h"
- #include "ui/boxes/confirm_box.h"
- #include "ui/boxes/edit_birthday_box.h"
- #include "ui/integration.h"
- #include "payments/payments_non_panel_process.h"
- #include "boxes/peers/edit_peer_info_box.h"
- #include "boxes/share_box.h"
- #include "boxes/connection_box.h"
- #include "boxes/gift_premium_box.h"
- #include "boxes/edit_privacy_box.h"
- #include "boxes/premium_preview_box.h"
- #include "boxes/sticker_set_box.h"
- #include "boxes/star_gift_box.h"
- #include "boxes/language_box.h"
- #include "passport/passport_form_controller.h"
- #include "ui/text/text_utilities.h"
- #include "ui/toast/toast.h"
- #include "data/components/credits.h"
- #include "data/data_birthday.h"
- #include "data/data_channel.h"
- #include "data/data_document.h"
- #include "data/data_session.h"
- #include "data/data_user.h"
- #include "media/player/media_player_instance.h"
- #include "media/view/media_view_open_common.h"
- #include "window/window_session_controller.h"
- #include "window/window_session_controller_link_info.h"
- #include "window/window_controller.h"
- #include "window/window_peer_menu.h"
- #include "window/themes/window_theme_editor_box.h" // GenerateSlug.
- #include "payments/payments_checkout_process.h"
- #include "settings/settings_active_sessions.h"
- #include "settings/settings_credits.h"
- #include "settings/settings_credits_graphics.h"
- #include "settings/settings_information.h"
- #include "settings/settings_global_ttl.h"
- #include "settings/settings_folders.h"
- #include "settings/settings_main.h"
- #include "settings/settings_privacy_controllers.h"
- #include "settings/settings_privacy_security.h"
- #include "settings/settings_chat.h"
- #include "settings/settings_premium.h"
- #include "storage/storage_account.h"
- #include "mainwidget.h"
- #include "main/main_account.h"
- #include "main/main_app_config.h"
- #include "main/main_domain.h"
- #include "main/main_session.h"
- #include "main/main_session_settings.h"
- #include "info/info_controller.h"
- #include "info/info_memento.h"
- #include "inline_bots/bot_attach_web_view.h"
- #include "history/history.h"
- #include "history/history_item.h"
- #include "apiwrap.h"
- #include <QtGui/QGuiApplication>
- namespace Core {
- namespace {
- using Match = qthelp::RegularExpressionMatch;
- class PersonalChannelController final : public PeerListController {
- public:
- explicit PersonalChannelController(
- not_null<Window::SessionController*> window);
- ~PersonalChannelController();
- Main::Session &session() const override;
- void prepare() override;
- void rowClicked(not_null<PeerListRow*> row) override;
- [[nodiscard]] rpl::producer<not_null<ChannelData*>> chosen() const;
- private:
- const not_null<Window::SessionController*> _window;
- rpl::event_stream<not_null<ChannelData*>> _chosen;
- mtpRequestId _requestId = 0;
- };
- PersonalChannelController::PersonalChannelController(
- not_null<Window::SessionController*> window)
- : _window(window) {
- }
- PersonalChannelController::~PersonalChannelController() {
- if (_requestId) {
- _window->session().api().request(_requestId).cancel();
- }
- }
- Main::Session &PersonalChannelController::session() const {
- return _window->session();
- }
- void PersonalChannelController::prepare() {
- setDescription(object_ptr<Ui::FlatLabel>(
- nullptr,
- tr::lng_contacts_loading(),
- computeListSt().about));
- using Flag = MTPchannels_GetAdminedPublicChannels::Flag;
- _requestId = _window->session().api().request(
- MTPchannels_GetAdminedPublicChannels(
- MTP_flags(Flag::f_for_personal))
- ).done([=](const MTPmessages_Chats &result) {
- _requestId = 0;
- setDescription(nullptr);
- const auto &chats = result.match([](const auto &data) {
- return data.vchats().v;
- });
- const auto owner = &_window->session().data();
- for (const auto &chat : chats) {
- if (const auto peer = owner->processChat(chat)) {
- const auto rowId = peer->id.value;
- const auto channel = peer->asChannel();
- if (channel && !delegate()->peerListFindRow(rowId)) {
- auto row = std::make_unique<PeerListRow>(peer);
- row->setCustomStatus(tr::lng_chat_status_subscribers(
- tr::now,
- lt_count,
- channel->membersCount()));
- delegate()->peerListAppendRow(std::move(row));
- }
- }
- }
- if (!delegate()->peerListFullRowsCount()) {
- auto none = rpl::combine(
- tr::lng_settings_channel_no_yet(Ui::Text::WithEntities),
- tr::lng_settings_channel_start()
- ) | rpl::map([](TextWithEntities &&text, const QString &link) {
- return text.append('\n').append(Ui::Text::Link(link));
- });
- auto label = object_ptr<Ui::FlatLabel>(
- nullptr,
- std::move(none),
- computeListSt().about);
- label->setClickHandlerFilter([=](const auto &...) {
- _window->showNewChannel();
- return false;
- });
- setDescription(std::move(label));
- }
- delegate()->peerListRefreshRows();
- }).send();
- }
- void PersonalChannelController::rowClicked(not_null<PeerListRow*> row) {
- if (const auto channel = row->peer()->asChannel()) {
- _chosen.fire_copy(channel);
- }
- }
- auto PersonalChannelController::chosen() const
- -> rpl::producer<not_null<ChannelData*>> {
- return _chosen.events();
- }
- Window::SessionController *ApplyAccountIndex(
- not_null<Window::SessionController*> controller,
- int accountIndex) {
- if (accountIndex <= 0) {
- return nullptr;
- }
- const auto list = Core::App().domain().orderedAccounts();
- if (accountIndex > int(list.size())) {
- return nullptr;
- }
- const auto account = list[accountIndex - 1];
- if (account == &controller->session().account()) {
- return controller;
- } else if (const auto window = Core::App().windowFor({ account })) {
- if (&window->account() != account) {
- Core::App().domain().maybeActivate(account);
- if (&window->account() != account) {
- return nullptr;
- }
- }
- const auto session = window->sessionController();
- if (session) {
- return session;
- }
- }
- return nullptr;
- }
- void SavePersonalChannel(
- not_null<Window::SessionController*> window,
- ChannelData *channel) {
- const auto self = window->session().user();
- const auto history = channel
- ? channel->owner().history(channel->id).get()
- : nullptr;
- const auto item = history
- ? history->lastServerMessage()
- : nullptr;
- const auto channelId = channel
- ? peerToChannel(channel->id)
- : ChannelId();
- const auto messageId = item ? item->id : MsgId();
- if (self->personalChannelId() != channelId
- || (messageId
- && self->personalChannelMessageId() != messageId)) {
- self->setPersonalChannel(channelId, messageId);
- self->session().api().request(MTPaccount_UpdatePersonalChannel(
- channel ? channel->inputChannel : MTP_inputChannelEmpty()
- )).done(crl::guard(window, [=] {
- window->showToast((channel
- ? tr::lng_settings_channel_saved
- : tr::lng_settings_channel_removed)(tr::now));
- })).fail(crl::guard(window, [=](const MTP::Error &error) {
- window->showToast(u"Error: "_q + error.type());
- })).send();
- }
- }
- bool JoinGroupByHash(
- Window::SessionController *controller,
- const Match &match,
- const QVariant &context) {
- if (!controller) {
- return false;
- }
- Api::CheckChatInvite(controller, match->captured(1));
- controller->window().activate();
- return true;
- }
- bool JoinFilterBySlug(
- Window::SessionController *controller,
- const Match &match,
- const QVariant &context) {
- if (!controller) {
- return false;
- }
- Api::CheckFilterInvite(controller, match->captured(1));
- controller->window().activate();
- return true;
- }
- bool ShowStickerSet(
- Window::SessionController *controller,
- const Match &match,
- const QVariant &context) {
- if (!controller) {
- return false;
- }
- Core::App().hideMediaView();
- controller->show(Box<StickerSetBox>(
- controller->uiShow(),
- StickerSetIdentifier{ .shortName = match->captured(2) },
- (match->captured(1) == "addemoji"
- ? Data::StickersType::Emoji
- : Data::StickersType::Stickers)));
- controller->window().activate();
- return true;
- }
- bool ShowTheme(
- Window::SessionController *controller,
- const Match &match,
- const QVariant &context) {
- if (!controller) {
- return false;
- }
- const auto fromMessageId = context.value<ClickHandlerContext>().itemId;
- Core::App().hideMediaView();
- controller->session().data().cloudThemes().resolve(
- &controller->window(),
- match->captured(1),
- fromMessageId);
- controller->window().activate();
- return true;
- }
- void ShowLanguagesBox(Window::SessionController *controller) {
- static auto Guard = base::binary_guard();
- Guard = LanguageBox::Show(controller);
- }
- void ShowPhonePrivacyBox(Window::SessionController *controller) {
- static auto Guard = base::binary_guard();
- auto guard = base::binary_guard();
- using Privacy = Api::UserPrivacy;
- const auto key = Privacy::Key::PhoneNumber;
- controller->session().api().userPrivacy().reload(key);
- const auto weak = base::make_weak(controller);
- auto shared = std::make_shared<base::binary_guard>(
- guard.make_guard());
- auto lifetime = std::make_shared<rpl::lifetime>();
- controller->session().api().userPrivacy().value(
- key
- ) | rpl::take(
- 1
- ) | rpl::start_with_next([=](const Privacy::Rule &value) mutable {
- using namespace ::Settings;
- const auto show = shared->alive();
- if (lifetime) {
- base::take(lifetime)->destroy();
- }
- if (show) {
- if (const auto controller = weak.get()) {
- controller->show(Box<EditPrivacyBox>(
- controller,
- std::make_unique<PhoneNumberPrivacyController>(
- controller),
- value));
- }
- }
- }, *lifetime);
- Guard = std::move(guard);
- }
- bool SetLanguage(
- Window::SessionController *controller,
- const Match &match,
- const QVariant &context) {
- if (match->capturedView(1).isEmpty()) {
- ShowLanguagesBox(controller);
- } else {
- const auto languageId = match->captured(2);
- Lang::CurrentCloudManager().switchWithWarning(languageId);
- }
- if (controller) {
- controller->window().activate();
- }
- return true;
- }
- bool ShareUrl(
- Window::SessionController *controller,
- const Match &match,
- const QVariant &context) {
- if (!controller) {
- return false;
- }
- const auto params = url_parse_params(
- match->captured(1),
- qthelp::UrlParamNameTransform::ToLower);
- const auto url = params.value(u"url"_q);
- if (url.isEmpty() || url.trimmed().startsWith('@')) {
- // Don't allow to insert an inline bot query by share url link.
- return false;
- }
- const auto text = params.value("text");
- const auto chosen = [=](not_null<Data::Thread*> thread) {
- const auto content = controller->content();
- return content->shareUrl(thread, url, text);
- };
- Window::ShowChooseRecipientBox(controller, chosen);
- controller->window().activate();
- return true;
- }
- bool ConfirmPhone(
- Window::SessionController *controller,
- const Match &match,
- const QVariant &context) {
- if (!controller) {
- return false;
- }
- const auto params = url_parse_params(
- match->captured(1),
- qthelp::UrlParamNameTransform::ToLower);
- const auto phone = params.value(u"phone"_q);
- const auto hash = params.value(u"hash"_q);
- if (phone.isEmpty() || hash.isEmpty()) {
- return false;
- }
- controller->session().api().confirmPhone().resolve(
- controller,
- phone,
- hash);
- controller->window().activate();
- return true;
- }
- bool ApplySocksProxy(
- Window::SessionController *controller,
- const Match &match,
- const QVariant &context) {
- auto params = url_parse_params(
- match->captured(1),
- qthelp::UrlParamNameTransform::ToLower);
- ProxiesBoxController::ShowApplyConfirmation(
- controller,
- MTP::ProxyData::Type::Socks5,
- params);
- if (controller) {
- controller->window().activate();
- }
- return true;
- }
- bool ApplyMtprotoProxy(
- Window::SessionController *controller,
- const Match &match,
- const QVariant &context) {
- auto params = url_parse_params(
- match->captured(1),
- qthelp::UrlParamNameTransform::ToLower);
- ProxiesBoxController::ShowApplyConfirmation(
- controller,
- MTP::ProxyData::Type::Mtproto,
- params);
- if (controller) {
- controller->window().activate();
- }
- return true;
- }
- bool ShowPassportForm(
- Window::SessionController *controller,
- const QMap<QString, QString> ¶ms) {
- if (!controller) {
- return false;
- }
- const auto botId = params.value("bot_id", QString()).toULongLong();
- const auto scope = params.value("scope", QString());
- const auto callback = params.value("callback_url", QString());
- const auto publicKey = params.value("public_key", QString());
- const auto nonce = params.value(
- Passport::NonceNameByScope(scope),
- QString());
- controller->showPassportForm(Passport::FormRequest(
- botId,
- scope,
- callback,
- publicKey,
- nonce));
- return true;
- }
- bool ShowPassport(
- Window::SessionController *controller,
- const Match &match,
- const QVariant &context) {
- return ShowPassportForm(
- controller,
- url_parse_params(
- match->captured(1),
- qthelp::UrlParamNameTransform::ToLower));
- }
- bool ShowWallPaper(
- Window::SessionController *controller,
- const Match &match,
- const QVariant &context) {
- if (!controller) {
- return false;
- }
- const auto params = url_parse_params(
- match->captured(1),
- qthelp::UrlParamNameTransform::ToLower);
- // const auto bg = params.value(u"bg_color"_q);
- const auto color = params.value(u"color"_q);
- const auto gradient = params.value(u"gradient"_q);
- const auto result = BackgroundPreviewBox::Start(
- controller,
- (!color.isEmpty()
- ? color
- : !gradient.isEmpty()
- ? gradient
- : params.value(u"slug"_q)),
- params);
- controller->window().activate();
- return result;
- }
- [[nodiscard]] ChatAdminRights ParseRequestedAdminRights(
- const QString &value) {
- auto result = ChatAdminRights();
- for (const auto &element : value.split(QRegularExpression(u"[+ ]"_q))) {
- if (element == u"change_info"_q) {
- result |= ChatAdminRight::ChangeInfo;
- } else if (element == u"post_messages"_q) {
- result |= ChatAdminRight::PostMessages;
- } else if (element == u"edit_messages"_q) {
- result |= ChatAdminRight::EditMessages;
- } else if (element == u"delete_messages"_q) {
- result |= ChatAdminRight::DeleteMessages;
- } else if (element == u"restrict_members"_q) {
- result |= ChatAdminRight::BanUsers;
- } else if (element == u"invite_users"_q) {
- result |= ChatAdminRight::InviteByLinkOrAdd;
- } else if (element == u"manage_topics"_q) {
- result |= ChatAdminRight::ManageTopics;
- } else if (element == u"pin_messages"_q) {
- result |= ChatAdminRight::PinMessages;
- } else if (element == u"promote_members"_q) {
- result |= ChatAdminRight::AddAdmins;
- } else if (element == u"manage_video_chats"_q) {
- result |= ChatAdminRight::ManageCall;
- } else if (element == u"anonymous"_q) {
- result |= ChatAdminRight::Anonymous;
- } else if (element == u"manage_chat"_q) {
- result |= ChatAdminRight::Other;
- } else {
- return {};
- }
- }
- return result;
- }
- bool ResolveUsernameOrPhone(
- Window::SessionController *controller,
- const Match &match,
- const QVariant &context) {
- if (!controller) {
- return false;
- }
- const auto params = url_parse_params(
- match->captured(1),
- qthelp::UrlParamNameTransform::ToLower);
- if (params.contains(u"acc"_q)) {
- const auto switched = ApplyAccountIndex(
- controller,
- params.value(u"acc"_q).toInt());
- if (switched) {
- controller = switched;
- } else {
- controller->showToast(u"Could not activate account %1."_q.arg(
- params.value(u"acc"_q)));
- return false;
- }
- }
- const auto domainParam = params.value(u"domain"_q);
- const auto appnameParam = params.value(u"appname"_q);
- const auto myContext = context.value<ClickHandlerContext>();
- if (domainParam == u"giftcode"_q && !appnameParam.isEmpty()) {
- const auto itemId = myContext.itemId;
- const auto item = controller->session().data().message(itemId);
- const auto fromId = item ? item->from()->id : PeerId();
- const auto selfId = controller->session().userPeerId();
- const auto toId = !item
- ? PeerId()
- : (fromId == selfId)
- ? item->history()->peer->id
- : selfId;
- ResolveGiftCode(controller, appnameParam, fromId, toId);
- return true;
- }
- // Fix t.me/s/username links.
- const auto webChannelPreviewLink = (domainParam == u"s"_q)
- && !appnameParam.isEmpty();
- const auto domain = webChannelPreviewLink ? appnameParam : domainParam;
- const auto phone = params.value(u"phone"_q);
- const auto validDomain = [](const QString &domain) {
- return qthelp::regex_match(
- u"^[a-zA-Z0-9\\.\\_]+$"_q,
- domain,
- {}
- ).valid();
- };
- const auto validPhone = [](const QString &phone) {
- return qthelp::regex_match(u"^[0-9]+$"_q, phone, {}).valid();
- };
- if (domain == u"telegrampassport"_q) {
- return ShowPassportForm(controller, params);
- } else if (!validDomain(domain) && !validPhone(phone)) {
- return false;
- }
- using ResolveType = Window::ResolveType;
- auto resolveType = params.contains(u"profile"_q)
- ? ResolveType::Profile
- : ResolveType::Default;
- auto startToken = params.value(u"start"_q);
- auto referral = params.value(u"ref"_q);
- if (!startToken.isEmpty()) {
- resolveType = ResolveType::BotStart;
- if (referral.isEmpty()) {
- const auto appConfig = &controller->session().appConfig();
- const auto &prefixes = appConfig->startRefPrefixes();
- for (const auto &prefix : prefixes) {
- if (startToken.startsWith(prefix)) {
- referral = startToken.mid(prefix.size());
- break;
- }
- }
- }
- } else if (params.contains(u"startgroup"_q)) {
- resolveType = ResolveType::AddToGroup;
- startToken = params.value(u"startgroup"_q);
- } else if (params.contains(u"startchannel"_q)) {
- resolveType = ResolveType::AddToChannel;
- } else if (params.contains(u"boost"_q)) {
- resolveType = ResolveType::Boost;
- }
- auto post = ShowAtUnreadMsgId;
- auto adminRights = ChatAdminRights();
- if (resolveType == ResolveType::AddToGroup
- || resolveType == ResolveType::AddToChannel) {
- adminRights = ParseRequestedAdminRights(params.value(u"admin"_q));
- }
- const auto postParam = params.value(u"post"_q);
- if (const auto postId = postParam.toInt()) {
- post = postId;
- }
- const auto storyParam = params.value(u"story"_q);
- const auto storyId = storyParam.toInt();
- const auto appname = webChannelPreviewLink ? QString() : appnameParam;
- const auto commentParam = params.value(u"comment"_q);
- const auto commentId = commentParam.toInt();
- const auto topicParam = params.value(u"topic"_q);
- const auto topicId = topicParam.toInt();
- const auto threadParam = params.value(u"thread"_q);
- const auto threadId = topicId ? topicId : threadParam.toInt();
- const auto gameParam = params.value(u"game"_q);
- const auto videot = params.value(u"t"_q);
- if (!gameParam.isEmpty() && validDomain(gameParam)) {
- startToken = gameParam;
- resolveType = ResolveType::ShareGame;
- }
- if (!appname.isEmpty()) {
- resolveType = ResolveType::BotApp;
- if (startToken.isEmpty() && params.contains(u"startapp"_q)) {
- startToken = params.value(u"startapp"_q);
- }
- }
- controller->window().activate();
- controller->showPeerByLink(Window::PeerByLinkInfo{
- .usernameOrId = domain,
- .phone = phone,
- .messageId = post,
- .storyId = storyId,
- .videoTimestamp = (!videot.isEmpty()
- ? ParseVideoTimestamp(videot)
- : std::optional<TimeId>()),
- .text = params.value(u"text"_q),
- .repliesInfo = commentId
- ? Window::RepliesByLinkInfo{
- Window::CommentId{ commentId }
- }
- : threadId
- ? Window::RepliesByLinkInfo{
- Window::ThreadId{ threadId }
- }
- : Window::RepliesByLinkInfo{ v::null },
- .resolveType = resolveType,
- .referral = referral,
- .startToken = startToken,
- .startAdminRights = adminRights,
- .startAutoSubmit = myContext.botStartAutoSubmit,
- .botAppName = (appname.isEmpty() ? postParam : appname),
- .botAppForceConfirmation = myContext.mayShowConfirmation,
- .botAppFullScreen = (params.value(u"mode"_q) == u"fullscreen"_q),
- .attachBotUsername = params.value(u"attach"_q),
- .attachBotToggleCommand = (params.contains(u"startattach"_q)
- ? params.value(u"startattach"_q)
- : (appname.isEmpty() && params.contains(u"startapp"_q))
- ? params.value(u"startapp"_q)
- : std::optional<QString>()),
- .attachBotMainOpen = (appname.isEmpty()
- && params.contains(u"startapp"_q)),
- .attachBotMainCompact = (appname.isEmpty()
- && params.contains(u"startapp"_q)
- && (params.value(u"mode"_q) == u"compact"_q)),
- .attachBotChooseTypes = InlineBots::ParseChooseTypes(
- params.value(u"choose"_q)),
- .voicechatHash = (params.contains(u"livestream"_q)
- ? std::make_optional(params.value(u"livestream"_q))
- : params.contains(u"videochat"_q)
- ? std::make_optional(params.value(u"videochat"_q))
- : params.contains(u"voicechat"_q)
- ? std::make_optional(params.value(u"voicechat"_q))
- : std::nullopt),
- .clickFromMessageId = myContext.itemId,
- .clickFromBotWebviewContext = myContext.botWebviewContext,
- });
- return true;
- }
- bool ResolvePrivatePost(
- Window::SessionController *controller,
- const Match &match,
- const QVariant &context) {
- if (!controller) {
- return false;
- }
- const auto params = url_parse_params(
- match->captured(1),
- qthelp::UrlParamNameTransform::ToLower);
- const auto channelId = ChannelId(
- params.value(u"channel"_q).toULongLong());
- const auto msgId = params.value(u"post"_q).toInt();
- const auto commentParam = params.value(u"comment"_q);
- const auto commentId = commentParam.toInt();
- const auto topicParam = params.value(u"topic"_q);
- const auto topicId = topicParam.toInt();
- const auto threadParam = params.value(u"thread"_q);
- const auto threadId = topicId ? topicId : threadParam.toInt();
- if (!channelId || (msgId && !IsServerMsgId(msgId))) {
- return false;
- }
- const auto my = context.value<ClickHandlerContext>();
- controller->showPeerByLink(Window::PeerByLinkInfo{
- .usernameOrId = channelId,
- .messageId = msgId,
- .repliesInfo = commentId
- ? Window::RepliesByLinkInfo{
- Window::CommentId{ commentId }
- }
- : threadId
- ? Window::RepliesByLinkInfo{
- Window::ThreadId{ threadId }
- }
- : Window::RepliesByLinkInfo{ v::null },
- .clickFromMessageId = my.itemId,
- .clickFromBotWebviewContext = my.botWebviewContext,
- });
- controller->window().activate();
- return true;
- }
- bool ResolveSettings(
- Window::SessionController *controller,
- const Match &match,
- const QVariant &context) {
- const auto section = match->captured(1).mid(1).toLower();
- const auto type = [&]() -> std::optional<::Settings::Type> {
- if (section == u"language"_q) {
- ShowLanguagesBox(controller);
- return {};
- } else if (section == u"phone_privacy"_q) {
- ShowPhonePrivacyBox(controller);
- return {};
- } else if (section == u"devices"_q) {
- return ::Settings::Sessions::Id();
- } else if (section == u"folders"_q) {
- return ::Settings::Folders::Id();
- } else if (section == u"privacy"_q) {
- return ::Settings::PrivacySecurity::Id();
- } else if (section == u"themes"_q) {
- return ::Settings::Chat::Id();
- } else if (section == u"change_number"_q) {
- controller->show(
- Ui::MakeInformBox(tr::lng_change_phone_error()));
- return {};
- } else if (section == u"auto_delete"_q) {
- return ::Settings::GlobalTTLId();
- } else if (section == u"information"_q) {
- return ::Settings::Information::Id();
- }
- return ::Settings::Main::Id();
- }();
- if (type.has_value()) {
- if (!controller) {
- return false;
- } else if (*type == ::Settings::Sessions::Id()) {
- controller->session().api().authorizations().reload();
- }
- controller->showSettings(*type);
- controller->window().activate();
- }
- return true;
- }
- bool HandleUnknown(
- Window::SessionController *controller,
- const Match &match,
- const QVariant &context) {
- if (!controller) {
- return false;
- }
- const auto request = match->captured(1);
- const auto callback = crl::guard(controller, [=](
- TextWithEntities message,
- bool updateRequired) {
- if (updateRequired) {
- const auto callback = [=](Fn<void()> &&close) {
- Core::UpdateApplication();
- close();
- };
- controller->show(Ui::MakeConfirmBox({
- .text = message,
- .confirmed = callback,
- .confirmText = tr::lng_menu_update(),
- }));
- } else {
- controller->show(Ui::MakeInformBox(message));
- }
- });
- controller->session().api().requestDeepLinkInfo(request, callback);
- return true;
- }
- bool OpenMediaTimestamp(
- Window::SessionController *controller,
- const Match &match,
- const QVariant &context) {
- if (!controller) {
- return false;
- }
- const auto position = match->captured(2).toInt();
- if (position < 0) {
- return false;
- }
- const auto base = match->captured(1);
- if (base.startsWith(u"doc"_q)) {
- const auto parts = base.mid(3).split('_');
- const auto documentId = parts.value(0).toULongLong();
- const auto itemId = FullMsgId(
- PeerId(parts.value(1).toULongLong()),
- MsgId(parts.value(2).toLongLong()));
- const auto session = &controller->session();
- const auto document = session->data().document(documentId);
- const auto context = session->data().message(itemId);
- const auto time = position * crl::time(1000);
- if (document->isVideoFile()) {
- controller->window().openInMediaView(Media::View::OpenRequest(
- controller,
- document,
- context,
- context ? context->topicRootId() : MsgId(0),
- false,
- time));
- } else if (document->isSong() || document->isVoiceMessage()) {
- session->local().setMediaLastPlaybackPosition(documentId, time);
- Media::Player::instance()->play({ document, itemId });
- }
- return true;
- }
- return false;
- }
- bool ShowInviteLink(
- Window::SessionController *controller,
- const Match &match,
- const QVariant &context) {
- if (!controller) {
- return false;
- }
- const auto base64link = match->captured(1).toLatin1();
- const auto link = QString::fromUtf8(QByteArray::fromBase64(base64link));
- if (link.isEmpty()) {
- return false;
- }
- QGuiApplication::clipboard()->setText(link);
- controller->showToast(tr::lng_group_invite_copied(tr::now));
- return true;
- }
- bool OpenExternalLink(
- Window::SessionController *controller,
- const Match &match,
- const QVariant &context) {
- return Ui::Integration::Instance().handleUrlClick(
- match->captured(1),
- context);
- }
- bool CopyPeerId(
- Window::SessionController *controller,
- const Match &match,
- const QVariant &context) {
- TextUtilities::SetClipboardText({ match->captured(1) });
- if (controller) {
- controller->showToast(u"ID copied to clipboard."_q);
- }
- return true;
- }
- bool ShowSearchTagsPromo(
- Window::SessionController *controller,
- const Match &match,
- const QVariant &context) {
- if (!controller) {
- return false;
- }
- ShowPremiumPreviewBox(controller, PremiumFeature::TagsForMessages);
- return true;
- }
- bool ShowEditBirthday(
- Window::SessionController *controller,
- const Match &match,
- const QVariant &context) {
- if (!controller) {
- return false;
- }
- const auto user = controller->session().user();
- const auto save = [=](Data::Birthday result) {
- user->setBirthday(result);
- using Flag = MTPaccount_UpdateBirthday::Flag;
- using BFlag = MTPDbirthday::Flag;
- user->session().api().request(MTPaccount_UpdateBirthday(
- MTP_flags(result ? Flag::f_birthday : Flag()),
- MTP_birthday(
- MTP_flags(result.year() ? BFlag::f_year : BFlag()),
- MTP_int(result.day()),
- MTP_int(result.month()),
- MTP_int(result.year()))
- )).done(crl::guard(controller, [=] {
- controller->showToast(tr::lng_settings_birthday_saved(tr::now));
- })).fail(crl::guard(controller, [=](const MTP::Error &error) {
- const auto type = error.type();
- controller->showToast(type.startsWith(u"FLOOD_WAIT_"_q)
- ? tr::lng_flood_error(tr::now)
- : (u"Error: "_q + error.type()));
- })).handleFloodErrors().send();
- };
- controller->show(Box(
- Ui::EditBirthdayBox,
- user->birthday(),
- save));
- return true;
- }
- bool ShowEditBirthdayPrivacy(
- Window::SessionController *controller,
- const Match &match,
- const QVariant &context) {
- if (!controller) {
- return false;
- }
- auto syncLifetime = controller->session().api().userPrivacy().value(
- Api::UserPrivacy::Key::Birthday
- ) | rpl::take(
- 1
- ) | rpl::start_with_next([=](const Api::UserPrivacy::Rule &value) {
- controller->show(Box<EditPrivacyBox>(
- controller,
- std::make_unique<::Settings::BirthdayPrivacyController>(),
- value));
- });
- return true;
- }
- bool ShowEditPersonalChannel(
- Window::SessionController *controller,
- const Match &match,
- const QVariant &context) {
- if (!controller) {
- return false;
- }
- auto listController = std::make_unique<PersonalChannelController>(
- controller);
- const auto rawController = listController.get();
- auto initBox = [=](not_null<PeerListBox*> box) {
- box->setTitle(tr::lng_settings_channel_label());
- box->addButton(tr::lng_box_done(), [=] {
- box->closeBox();
- });
- const auto save = [=](ChannelData *channel) {
- SavePersonalChannel(controller, channel);
- box->closeBox();
- };
- rawController->chosen(
- ) | rpl::start_with_next([=](not_null<ChannelData*> channel) {
- save(channel);
- }, box->lifetime());
- if (controller->session().user()->personalChannelId()) {
- box->addLeftButton(tr::lng_settings_channel_remove(), [=] {
- save(nullptr);
- });
- }
- };
- controller->show(Box<PeerListBox>(
- std::move(listController),
- std::move(initBox)));
- return true;
- }
- bool ShowCollectiblePhone(
- Window::SessionController *controller,
- const Match &match,
- const QVariant &context) {
- if (!controller) {
- return false;
- }
- const auto phone = match->captured(1);
- const auto peerId = PeerId(match->captured(2).toULongLong());
- controller->resolveCollectible(
- peerId,
- phone.startsWith('+') ? phone : '+' + phone);
- return true;
- }
- bool ShowCollectibleUsername(
- Window::SessionController *controller,
- const Match &match,
- const QVariant &context) {
- if (!controller) {
- return false;
- }
- const auto username = match->captured(1);
- const auto peerId = PeerId(match->captured(2).toULongLong());
- controller->resolveCollectible(peerId, username);
- return true;
- }
- bool CopyUsernameLink(
- Window::SessionController *controller,
- const Match &match,
- const QVariant &context) {
- if (!controller) {
- return false;
- }
- const auto username = match->captured(1);
- TextUtilities::SetClipboardText({
- controller->session().createInternalLinkFull(username)
- });
- controller->showToast(tr::lng_username_copied(tr::now));
- return true;
- }
- bool CopyUsername(
- Window::SessionController *controller,
- const Match &match,
- const QVariant &context) {
- if (!controller) {
- return false;
- }
- const auto username = match->captured(1);
- TextUtilities::SetClipboardText({ '@' + username });
- controller->showToast(tr::lng_username_text_copied(tr::now));
- return true;
- }
- bool EditPaidMessagesFee(
- Window::SessionController *controller,
- const Match &match,
- const QVariant &context) {
- if (!controller) {
- return false;
- }
- const auto peerId = PeerId(match->captured(1).toULongLong());
- if (const auto id = peerToChannel(peerId)) {
- const auto channel = controller->session().data().channelLoaded(id);
- if (channel && channel->canEditPermissions()) {
- ShowEditChatPermissions(controller, channel);
- }
- } else {
- controller->show(Box(EditMessagesPrivacyBox, controller));
- }
- return true;
- }
- bool ShowCommonGroups(
- Window::SessionController *controller,
- const Match &match,
- const QVariant &context) {
- if (!controller) {
- return false;
- }
- const auto peerId = PeerId(match->captured(1).toULongLong());
- if (const auto id = peerToUser(peerId)) {
- const auto user = controller->session().data().userLoaded(id);
- if (user) {
- controller->showSection(
- std::make_shared<Info::Memento>(
- user,
- Info::Section::Type::CommonGroups));
- }
- }
- return true;
- }
- bool ShowStarsExamples(
- Window::SessionController *controller,
- const Match &match,
- const QVariant &context) {
- if (!controller) {
- return false;
- }
- controller->show(Dialogs::StarsExamplesBox(controller));
- return true;
- }
- bool ShowPopularAppsAbout(
- Window::SessionController *controller,
- const Match &match,
- const QVariant &context) {
- if (!controller) {
- return false;
- }
- controller->show(Dialogs::PopularAppsAboutBox(controller));
- return true;
- }
- void ExportTestChatTheme(
- not_null<Window::SessionController*> controller,
- not_null<const Data::CloudTheme*> theme) {
- const auto session = &controller->session();
- const auto show = controller->uiShow();
- const auto inputSettings = [&](Data::CloudThemeType type)
- -> std::optional<MTPInputThemeSettings> {
- const auto i = theme->settings.find(type);
- if (i == end(theme->settings)) {
- show->showToast(u"Something went wrong :("_q);
- return std::nullopt;
- }
- const auto &fields = i->second;
- if (!fields.paper
- || !fields.paper->isPattern()
- || fields.paper->backgroundColors().empty()
- || !fields.paper->hasShareUrl()) {
- show->showToast(u"Something went wrong :("_q);
- return std::nullopt;
- }
- const auto &bg = fields.paper->backgroundColors();
- const auto url = fields.paper->shareUrl(&show->session());
- const auto from = url.indexOf("bg/");
- const auto till = url.indexOf("?");
- if (from < 0 || till <= from) {
- show->showToast(u"Bad WallPaper link: "_q + url);
- return std::nullopt;
- }
- using Setting = MTPDinputThemeSettings::Flag;
- using Paper = MTPDwallPaperSettings::Flag;
- const auto color = [](const QColor &color) {
- const auto red = color.red();
- const auto green = color.green();
- const auto blue = color.blue();
- return int(((uint32(red) & 0xFFU) << 16)
- | ((uint32(green) & 0xFFU) << 8)
- | (uint32(blue) & 0xFFU));
- };
- const auto colors = [&](const std::vector<QColor> &colors) {
- auto result = QVector<MTPint>();
- result.reserve(colors.size());
- for (const auto &single : colors) {
- result.push_back(MTP_int(color(single)));
- }
- return result;
- };
- const auto slug = url.mid(from + 3, till - from - 3);
- const auto settings = Setting::f_wallpaper
- | Setting::f_wallpaper_settings
- | (fields.outgoingAccentColor
- ? Setting::f_outbox_accent_color
- : Setting(0))
- | (!fields.outgoingMessagesColors.empty()
- ? Setting::f_message_colors
- : Setting(0));
- const auto papers = Paper::f_background_color
- | Paper::f_intensity
- | (bg.size() > 1
- ? Paper::f_second_background_color
- : Paper(0))
- | (bg.size() > 2
- ? Paper::f_third_background_color
- : Paper(0))
- | (bg.size() > 3
- ? Paper::f_fourth_background_color
- : Paper(0));
- return MTP_inputThemeSettings(
- MTP_flags(settings),
- ((type == Data::CloudThemeType::Dark)
- ? MTP_baseThemeTinted()
- : MTP_baseThemeClassic()),
- MTP_int(color(fields.accentColor)),
- MTP_int(color(fields.outgoingAccentColor.value_or(
- Qt::black))),
- MTP_vector<MTPint>(colors(fields.outgoingMessagesColors)),
- MTP_inputWallPaperSlug(MTP_string(slug)),
- MTP_wallPaperSettings(
- MTP_flags(papers),
- MTP_int(color(bg[0])),
- MTP_int(color(bg.size() > 1 ? bg[1] : Qt::black)),
- MTP_int(color(bg.size() > 2 ? bg[2] : Qt::black)),
- MTP_int(color(bg.size() > 3 ? bg[3] : Qt::black)),
- MTP_int(fields.paper->patternIntensity()),
- MTP_int(0), // rotation
- MTPstring())); // emoticon
- };
- const auto light = inputSettings(Data::CloudThemeType::Light);
- if (!light) {
- return;
- }
- const auto dark = inputSettings(Data::CloudThemeType::Dark);
- if (!dark) {
- return;
- }
- session->api().request(MTPaccount_CreateTheme(
- MTP_flags(MTPaccount_CreateTheme::Flag::f_settings),
- MTP_string(Window::Theme::GenerateSlug()),
- MTP_string(theme->title + " Desktop"),
- MTPInputDocument(),
- MTP_vector<MTPInputThemeSettings>(QVector<MTPInputThemeSettings>{
- *light,
- *dark,
- })
- )).done([=](const MTPTheme &result) {
- const auto slug = Data::CloudTheme::Parse(session, result, true).slug;
- QGuiApplication::clipboard()->setText(
- session->createInternalLinkFull("addtheme/" + slug));
- show->showToast(tr::lng_background_link_copied(tr::now));
- }).fail([=](const MTP::Error &error) {
- show->showToast(u"Error: "_q + error.type());
- }).send();
- }
- bool ResolveTestChatTheme(
- Window::SessionController *controller,
- const Match &match,
- const QVariant &context) {
- if (!controller) {
- return false;
- }
- const auto params = url_parse_params(
- match->captured(1),
- qthelp::UrlParamNameTransform::ToLower);
- if (const auto history = controller->activeChatCurrent().history()) {
- controller->clearCachedChatThemes();
- const auto theme = history->owner().cloudThemes().updateThemeFromLink(
- history->peer->themeEmoji(),
- params);
- if (theme) {
- if (!params["export"].isEmpty()) {
- ExportTestChatTheme(controller, &*theme);
- }
- const auto recache = [&](Data::CloudThemeType type) {
- [[maybe_unused]] auto value = theme->settings.contains(type)
- ? controller->cachedChatThemeValue(
- *theme,
- Data::WallPaper(0),
- type)
- : nullptr;
- };
- recache(Data::CloudThemeType::Dark);
- recache(Data::CloudThemeType::Light);
- }
- }
- return true;
- }
- bool ResolveInvoice(
- Window::SessionController *controller,
- const Match &match,
- const QVariant &context) {
- if (!controller) {
- return false;
- }
- const auto params = url_parse_params(
- match->captured(1),
- qthelp::UrlParamNameTransform::ToLower);
- const auto slug = params.value(u"slug"_q);
- if (slug.isEmpty()) {
- return false;
- }
- const auto window = &controller->window();
- Payments::CheckoutProcess::Start(
- &controller->session(),
- slug,
- crl::guard(window, [=](auto) { window->activate(); }),
- Payments::ProcessNonPanelPaymentFormFactory(controller));
- return true;
- }
- bool ResolvePremiumOffer(
- Window::SessionController *controller,
- const Match &match,
- const QVariant &context) {
- if (!controller) {
- return false;
- }
- const auto params = url_parse_params(
- match->captured(1).mid(1),
- qthelp::UrlParamNameTransform::ToLower);
- const auto refAddition = params.value(u"ref"_q);
- const auto ref = "deeplink"
- + (refAddition.isEmpty() ? QString() : '_' + refAddition);
- ::Settings::ShowPremium(controller, ref);
- controller->window().activate();
- return true;
- }
- bool ResolvePremiumMultigift(
- Window::SessionController *controller,
- const Match &match,
- const QVariant &context) {
- if (!controller) {
- return false;
- }
- Ui::ChooseStarGiftRecipient(controller);
- controller->window().activate();
- return true;
- }
- bool ResolveLoginCode(
- Window::SessionController *controller,
- const Match &match,
- const QVariant &context) {
- const auto loginCode = match->captured(2);
- const auto &domain = Core::App().domain();
- if (loginCode.isEmpty() || (!controller && !domain.started())) {
- return false;
- };
- (controller
- ? controller->session().account()
- : domain.active()).handleLoginCode(loginCode);
- if (controller) {
- controller->window().activate();
- } else if (const auto window = Core::App().activeWindow()) {
- window->activate();
- }
- return true;
- }
- bool ResolveBoost(
- Window::SessionController *controller,
- const Match &match,
- const QVariant &context) {
- if (!controller) {
- return false;
- }
- const auto params = url_parse_params(
- match->captured(1),
- qthelp::UrlParamNameTransform::ToLower);
- const auto domainParam = params.value(u"domain"_q);
- const auto channelParam = params.contains(u"c"_q)
- ? params.value(u"c"_q)
- : params.value(u"channel"_q);
- const auto myContext = context.value<ClickHandlerContext>();
- controller->window().activate();
- controller->showPeerByLink(Window::PeerByLinkInfo{
- .usernameOrId = (!domainParam.isEmpty()
- ? std::variant<QString, ChannelId>(domainParam)
- : ChannelId(BareId(channelParam.toULongLong()))),
- .resolveType = Window::ResolveType::Boost,
- .clickFromMessageId = myContext.itemId,
- });
- return true;
- }
- bool ResolveTopUp(
- Window::SessionController *controller,
- const Match &match,
- const QVariant &context) {
- if (!controller) {
- return false;
- }
- const auto params = url_parse_params(
- match->captured(1),
- qthelp::UrlParamNameTransform::ToLower);
- const auto amount = std::clamp(
- params.value(u"balance"_q).toULongLong(),
- qulonglong(1),
- qulonglong(1'000'000));
- const auto purpose = params.value(u"purpose"_q);
- const auto weak = base::make_weak(controller);
- const auto done = [=](::Settings::SmallBalanceResult result) {
- if (result == ::Settings::SmallBalanceResult::Already) {
- if (const auto strong = weak.get()) {
- const auto filter = [=](const auto &...) {
- strong->showSettings(::Settings::CreditsId());
- return false;
- };
- strong->showToast(Ui::Toast::Config{
- .text = tr::lng_credits_enough(
- tr::now,
- lt_link,
- Ui::Text::Link(
- Ui::Text::Bold(
- tr::lng_credits_enough_link(tr::now))),
- Ui::Text::RichLangValue),
- .filter = filter,
- .duration = 4 * crl::time(1000),
- });
- }
- }
- };
- ::Settings::MaybeRequestBalanceIncrease(
- controller->uiShow(),
- amount,
- ::Settings::SmallBalanceDeepLink{ .purpose = purpose },
- done);
- return true;
- }
- bool ResolveChatLink(
- Window::SessionController *controller,
- const Match &match,
- const QVariant &context) {
- if (!controller) {
- return false;
- }
- const auto myContext = context.value<ClickHandlerContext>();
- const auto slug = match->captured(1);
- controller->window().activate();
- controller->showPeerByLink(Window::PeerByLinkInfo{
- .chatLinkSlug = match->captured(1),
- .clickFromMessageId = myContext.itemId,
- .clickFromBotWebviewContext = myContext.botWebviewContext,
- });
- return true;
- }
- bool ResolveUniqueGift(
- Window::SessionController *controller,
- const Match &match,
- const QVariant &context) {
- if (!controller) {
- return false;
- }
- const auto slug = match->captured(1);
- if (slug.isEmpty()) {
- return false;
- }
- ResolveAndShowUniqueGift(controller->uiShow(), slug);
- return true;
- }
- } // namespace
- const std::vector<LocalUrlHandler> &LocalUrlHandlers() {
- static auto Result = std::vector<LocalUrlHandler>{
- {
- u"^join/?\\?invite=([a-zA-Z0-9\\.\\_\\-]+)(&|$)"_q,
- JoinGroupByHash
- },
- {
- u"^addlist/?\\?slug=([a-zA-Z0-9\\.\\_\\-]+)(&|$)"_q,
- JoinFilterBySlug
- },
- {
- u"^(addstickers|addemoji)/?\\?set=([a-zA-Z0-9\\.\\_]+)(&|$)"_q,
- ShowStickerSet
- },
- {
- u"^addtheme/?\\?slug=([a-zA-Z0-9\\.\\_]+)(&|$)"_q,
- ShowTheme
- },
- {
- u"^setlanguage/?(\\?lang=([a-zA-Z0-9\\.\\_\\-]+))?(&|$)"_q,
- SetLanguage
- },
- {
- u"^msg_url/?\\?(.+)(#|$)"_q,
- ShareUrl
- },
- {
- u"^confirmphone/?\\?(.+)(#|$)"_q,
- ConfirmPhone
- },
- {
- u"^socks/?\\?(.+)(#|$)"_q,
- ApplySocksProxy
- },
- {
- u"^proxy/?\\?(.+)(#|$)"_q,
- ApplyMtprotoProxy
- },
- {
- u"^passport/?\\?(.+)(#|$)"_q,
- ShowPassport
- },
- {
- u"^bg/?\\?(.+)(#|$)"_q,
- ShowWallPaper
- },
- {
- u"^resolve/?\\?(.+)(#|$)"_q,
- ResolveUsernameOrPhone
- },
- {
- u"^privatepost/?\\?(.+)(#|$)"_q,
- ResolvePrivatePost
- },
- {
- u"^settings(/language|/devices|/folders|/privacy|/themes|/change_number|/auto_delete|/information|/edit_profile|/phone_privacy)?$"_q,
- ResolveSettings
- },
- {
- u"^test_chat_theme/?\\?(.+)(#|$)"_q,
- ResolveTestChatTheme,
- },
- {
- u"^invoice/?\\?(.+)(#|$)"_q,
- ResolveInvoice,
- },
- {
- u"^premium_offer/?(\\?.+)?(#|$)"_q,
- ResolvePremiumOffer,
- },
- {
- u"^premium_multigift/?\\?(.+)(#|$)"_q,
- ResolvePremiumMultigift,
- },
- {
- u"^login/?(\\?code=([0-9]+))(&|$)"_q,
- ResolveLoginCode
- },
- {
- u"^boost/?\\?(.+)(#|$)"_q,
- ResolveBoost,
- },
- {
- u"^message/?\\?slug=([a-zA-Z0-9\\.\\_\\-]+)(&|$)"_q,
- ResolveChatLink
- },
- {
- u"^stars_topup/?\\?(.+)(#|$)"_q,
- ResolveTopUp
- },
- {
- u"^nft/?\\?slug=([a-zA-Z0-9\\.\\_\\-]+)(&|$)"_q,
- ResolveUniqueGift
- },
- {
- u"^([^\\?]+)(\\?|#|$)"_q,
- HandleUnknown
- },
- };
- return Result;
- }
- const std::vector<LocalUrlHandler> &InternalUrlHandlers() {
- static auto Result = std::vector<LocalUrlHandler>{
- {
- u"^media_timestamp/?\\?base=([a-zA-Z0-9\\.\\_\\-]+)&t=(\\d+)(&|$)"_q,
- OpenMediaTimestamp
- },
- {
- u"^show_invite_link/?\\?link=([a-zA-Z0-9_\\+\\/\\=\\-]+)(&|$)"_q,
- ShowInviteLink
- },
- {
- u"^url:(.+)$"_q,
- OpenExternalLink
- },
- {
- u"^copy:(.+)$"_q,
- CopyPeerId
- },
- {
- u"^about_tags$"_q,
- ShowSearchTagsPromo
- },
- {
- u"^edit_birthday$"_q,
- ShowEditBirthday,
- },
- {
- u"^edit_privacy_birthday$"_q,
- ShowEditBirthdayPrivacy,
- },
- {
- u"^edit_personal_channel$"_q,
- ShowEditPersonalChannel,
- },
- {
- u"^collectible_phone/([\\+0-9\\-\\s]+)@([0-9]+)$"_q,
- ShowCollectiblePhone,
- },
- {
- u"^collectible_username/([a-zA-Z0-9\\-\\_\\.]+)@([0-9]+)$"_q,
- ShowCollectibleUsername,
- },
- {
- u"^username_link/([a-zA-Z0-9\\-\\_\\.]+)@([0-9]+)$"_q,
- CopyUsernameLink,
- },
- {
- u"^username_regular/([a-zA-Z0-9\\-\\_\\.]+)@([0-9]+)$"_q,
- CopyUsername,
- },
- {
- u"^edit_paid_messages_fee/([0-9]+)$"_q,
- EditPaidMessagesFee,
- },
- {
- u"^common_groups/([0-9]+)$"_q,
- ShowCommonGroups,
- },
- {
- u"^stars_examples$"_q,
- ShowStarsExamples,
- },
- {
- u"^about_popular_apps$"_q,
- ShowPopularAppsAbout,
- },
- };
- return Result;
- }
- QString TryConvertUrlToLocal(QString url) {
- if (url.size() > 8192) {
- url = url.mid(0, 8192);
- }
- using namespace qthelp;
- auto matchOptions = RegExOption::CaseInsensitive;
- auto tonsiteMatch = (url.indexOf(u".ton") >= 0)
- ? regex_match(u"^(https?://)?[^/@:]+\\.ton($|/)"_q, url, matchOptions)
- : RegularExpressionMatch(QRegularExpressionMatch());
- if (tonsiteMatch) {
- const auto protocol = tonsiteMatch->captured(1);
- return u"tonsite://"_q + url.mid(protocol.size());
- }
- auto subdomainMatch = regex_match(u"^(https?://)?([a-zA-Z0-9\\_]+)\\.t\\.me(/\\d+)?/?(\\?.+)?"_q, url, matchOptions);
- if (subdomainMatch) {
- const auto name = subdomainMatch->captured(2);
- if (name.size() > 1 && name != "www") {
- const auto result = TryConvertUrlToLocal(
- subdomainMatch->captured(1)
- + "t.me/"
- + name
- + subdomainMatch->captured(3)
- + subdomainMatch->captured(4));
- return result.startsWith("tg://resolve?domain=")
- ? result
- : url;
- }
- }
- auto telegramMeMatch = regex_match(u"^(https?://)?(www\\.)?(telegram\\.(me|dog)|t\\.me)/(.+)$"_q, url, matchOptions);
- if (telegramMeMatch) {
- const auto query = telegramMeMatch->capturedView(5);
- if (const auto phoneMatch = regex_match(u"^\\+([0-9]+)(\\?|$)"_q, query, matchOptions)) {
- const auto params = query.mid(phoneMatch->captured(0).size()).toString();
- return u"tg://resolve?phone="_q + phoneMatch->captured(1) + (params.isEmpty() ? QString() : '&' + params);
- } else if (const auto joinChatMatch = regex_match(u"^(joinchat/|\\+|\\%20)([a-zA-Z0-9\\.\\_\\-]+)(\\?|$)"_q, query, matchOptions)) {
- return u"tg://join?invite="_q + url_encode(joinChatMatch->captured(2));
- } else if (const auto joinFilterMatch = regex_match(u"^(addlist/)([a-zA-Z0-9\\.\\_\\-]+)(\\?|$)"_q, query, matchOptions)) {
- return u"tg://addlist?slug="_q + url_encode(joinFilterMatch->captured(2));
- } else if (const auto stickerSetMatch = regex_match(u"^(addstickers|addemoji)/([a-zA-Z0-9\\.\\_]+)(\\?|$)"_q, query, matchOptions)) {
- return u"tg://"_q + stickerSetMatch->captured(1) + "?set=" + url_encode(stickerSetMatch->captured(2));
- } else if (const auto themeMatch = regex_match(u"^addtheme/([a-zA-Z0-9\\.\\_]+)(\\?|$)"_q, query, matchOptions)) {
- return u"tg://addtheme?slug="_q + url_encode(themeMatch->captured(1));
- } else if (const auto languageMatch = regex_match(u"^setlanguage/([a-zA-Z0-9\\.\\_\\-]+)(\\?|$)"_q, query, matchOptions)) {
- return u"tg://setlanguage?lang="_q + url_encode(languageMatch->captured(1));
- } else if (const auto shareUrlMatch = regex_match(u"^share/url/?\\?(.+)$"_q, query, matchOptions)) {
- return u"tg://msg_url?"_q + shareUrlMatch->captured(1);
- } else if (const auto confirmPhoneMatch = regex_match(u"^confirmphone/?\\?(.+)"_q, query, matchOptions)) {
- return u"tg://confirmphone?"_q + confirmPhoneMatch->captured(1);
- } else if (const auto ivMatch = regex_match(u"^iv/?\\?(.+)(#|$)"_q, query, matchOptions)) {
- //
- // 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(u"url"_q);
- //if (previewedUrl.startsWith(u"http://"_q, Qt::CaseInsensitive)
- // || previewedUrl.startsWith(u"https://"_q, Qt::CaseInsensitive)) {
- // return previewedUrl;
- //}
- return url;
- } else if (const auto socksMatch = regex_match(u"^socks/?\\?(.+)(#|$)"_q, query, matchOptions)) {
- return u"tg://socks?"_q + socksMatch->captured(1);
- } else if (const auto proxyMatch = regex_match(u"^proxy/?\\?(.+)(#|$)"_q, query, matchOptions)) {
- return u"tg://proxy?"_q + proxyMatch->captured(1);
- } else if (const auto invoiceMatch = regex_match(u"^(invoice/|\\$)([a-zA-Z0-9_\\-]+)(\\?|#|$)"_q, query, matchOptions)) {
- return u"tg://invoice?slug="_q + invoiceMatch->captured(2);
- } else if (const auto bgMatch = regex_match(u"^bg/([a-zA-Z0-9\\.\\_\\-\\~]+)(\\?(.+)?)?$"_q, query, matchOptions)) {
- const auto params = bgMatch->captured(3);
- const auto bg = bgMatch->captured(1);
- const auto type = regex_match(u"^[a-fA-F0-9]{6}^"_q, bg)
- ? "color"
- : (regex_match(u"^[a-fA-F0-9]{6}\\-[a-fA-F0-9]{6}$"_q, bg)
- || regex_match(u"^[a-fA-F0-9]{6}(\\~[a-fA-F0-9]{6}){1,3}$"_q, bg))
- ? "gradient"
- : "slug";
- return u"tg://bg?"_q + type + '=' + bg + (params.isEmpty() ? QString() : '&' + params);
- } else if (const auto chatlinkMatch = regex_match(u"^m/([a-zA-Z0-9\\.\\_\\-]+)(\\?|$)"_q, query, matchOptions)) {
- const auto slug = chatlinkMatch->captured(1);
- return u"tg://message?slug="_q + slug;
- } else if (const auto nftMatch = regex_match(u"^nft/([a-zA-Z0-9\\.\\_\\-]+)(\\?|$)"_q, query, matchOptions)) {
- const auto slug = nftMatch->captured(1);
- return u"tg://nft?slug="_q + slug;
- } else if (const auto privateMatch = regex_match(u"^"
- "c/(\\-?\\d+)"
- "("
- "/?\\?|"
- "/?$|"
- "/\\d+/?(\\?|$)|"
- "/\\d+/\\d+/?(\\?|$)"
- ")"_q, query, matchOptions)) {
- const auto channel = privateMatch->captured(1);
- const auto params = query.mid(privateMatch->captured(0).size()).toString();
- if (params.indexOf("boost", 0, Qt::CaseInsensitive) >= 0
- && params.toLower().split('&').contains(u"boost"_q)) {
- return u"tg://boost?channel="_q + channel;
- }
- const auto base = u"tg://privatepost?channel="_q + channel;
- auto added = QString();
- if (const auto threadPostMatch = regex_match(u"^/(\\d+)/(\\d+)(/?\\?|/?$)"_q, privateMatch->captured(2))) {
- added = u"&topic=%1&post=%2"_q.arg(threadPostMatch->captured(1), threadPostMatch->captured(2));
- } else if (const auto postMatch = regex_match(u"^/(\\d+)(/?\\?|/?$)"_q, privateMatch->captured(2))) {
- added = u"&post="_q + postMatch->captured(1);
- }
- return base + added + (params.isEmpty() ? QString() : '&' + params);
- } else if (const auto usernameMatch = regex_match(u"^"
- "([a-zA-Z0-9\\.\\_]+)"
- "("
- "/?\\?|"
- "/?$|"
- "/[a-zA-Z0-9\\.\\_\\-]+/?(\\?|$)|"
- "/\\d+/?(\\?|$)|"
- "/s/\\d+/?(\\?|$)|"
- "/\\d+/\\d+/?(\\?|$)"
- ")"_q, query, matchOptions)) {
- const auto domain = usernameMatch->captured(1);
- const auto params = query.mid(usernameMatch->captured(0).size()).toString();
- if (params.indexOf("boost", 0, Qt::CaseInsensitive) >= 0
- && params.toLower().split('&').contains(u"boost"_q)) {
- return u"tg://boost?domain="_q + domain;
- } else if (domain == u"boost"_q) {
- if (const auto domainMatch = regex_match(u"^/([a-zA-Z0-9\\.\\_]+)(/?\\?|/?$)"_q, usernameMatch->captured(2))) {
- return u"tg://boost?domain="_q + domainMatch->captured(1);
- } else if (params.indexOf("c=", 0, Qt::CaseInsensitive) >= 0) {
- return u"tg://boost?"_q + params;
- }
- }
- const auto base = u"tg://resolve?domain="_q + url_encode(usernameMatch->captured(1));
- auto added = QString();
- if (const auto threadPostMatch = regex_match(u"^/(\\d+)/(\\d+)(/?\\?|/?$)"_q, usernameMatch->captured(2))) {
- added = u"&topic=%1&post=%2"_q.arg(threadPostMatch->captured(1), threadPostMatch->captured(2));
- } else if (const auto postMatch = regex_match(u"^/(\\d+)(/?\\?|/?$)"_q, usernameMatch->captured(2))) {
- added = u"&post="_q + postMatch->captured(1);
- } else if (const auto storyMatch = regex_match(u"^/s/(\\d+)(/?\\?|/?$)"_q, usernameMatch->captured(2))) {
- added = u"&story="_q + storyMatch->captured(1);
- } else if (const auto appNameMatch = regex_match(u"^/([a-zA-Z0-9\\.\\_\\-]+)(/?\\?|/?$)"_q, usernameMatch->captured(2))) {
- added = u"&appname="_q + appNameMatch->captured(1);
- }
- return base + added + (params.isEmpty() ? QString() : '&' + params);
- }
- }
- return url;
- }
- bool InternalPassportLink(const QString &url) {
- const auto urlTrimmed = url.trimmed();
- if (!urlTrimmed.startsWith(u"tg://"_q, Qt::CaseInsensitive)) {
- return false;
- }
- const auto command = base::StringViewMid(urlTrimmed, u"tg://"_q.size());
- using namespace qthelp;
- const auto matchOptions = RegExOption::CaseInsensitive;
- const auto authMatch = regex_match(
- u"^passport/?\\?(.+)(#|$)"_q,
- command,
- matchOptions);
- const auto usernameMatch = regex_match(
- u"^resolve/?\\?(.+)(#|$)"_q,
- command,
- matchOptions);
- const auto usernameValue = usernameMatch->hasMatch()
- ? url_parse_params(
- usernameMatch->captured(1),
- UrlParamNameTransform::ToLower).value(u"domain"_q)
- : QString();
- const auto authLegacy = (usernameValue == u"telegrampassport"_q);
- return authMatch->hasMatch() || authLegacy;
- }
- bool StartUrlRequiresActivate(const QString &url) {
- return Core::App().passcodeLocked()
- ? true
- : !InternalPassportLink(url);
- }
- void ResolveAndShowUniqueGift(
- std::shared_ptr<ChatHelpers::Show> show,
- const QString &slug,
- ::Settings::CreditsEntryBoxStyleOverrides st) {
- struct Request {
- base::weak_ptr<Main::Session> weak;
- QString slug;
- mtpRequestId id = 0;
- };
- static auto request = Request();
- const auto session = &show->session();
- if (request.weak.get() == session && request.slug == slug) {
- return;
- } else if (const auto strong = request.weak.get()) {
- strong->api().request(request.id).cancel();
- }
- request.weak = session;
- request.slug = slug;
- const auto clear = [=] {
- if (request.weak.get() == session && request.slug == slug) {
- request = {};
- }
- };
- request.id = session->api().request(
- MTPpayments_GetUniqueStarGift(MTP_string(slug))
- ).done([=](const MTPpayments_UniqueStarGift &result) {
- clear();
- const auto &data = result.data();
- session->data().processUsers(data.vusers());
- if (const auto gift = Api::FromTL(session, data.vgift())) {
- using namespace ::Settings;
- show->show(Box(GlobalStarGiftBox, show, *gift, st));
- }
- }).fail([=](const MTP::Error &error) {
- clear();
- show->showToast(u"Error: "_q + error.type());
- }).send();
- }
- void ResolveAndShowUniqueGift(
- std::shared_ptr<ChatHelpers::Show> show,
- const QString &slug) {
- ResolveAndShowUniqueGift(std::move(show), slug, {});
- }
- TimeId ParseVideoTimestamp(QStringView value) {
- const auto kExp = u"^(?:(\\d+)h)?(?:(\\d+)m)?(?:(\\d+)s)?$"_q;
- const auto m = QRegularExpression(kExp).match(value);
- return m.hasMatch()
- ? (m.capturedView(1).toInt() * 3600
- + m.capturedView(2).toInt() * 60
- + m.capturedView(3).toInt())
- : value.toInt();
- }
- } // namespace Core
|