local_url_handlers.cpp 54 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853
  1. /*
  2. This file is part of Telegram Desktop,
  3. the official desktop application for the Telegram messaging service.
  4. For license and copyright information please follow this link:
  5. https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
  6. */
  7. #include "core/local_url_handlers.h"
  8. #include "api/api_authorizations.h"
  9. #include "api/api_confirm_phone.h"
  10. #include "api/api_chat_filters.h"
  11. #include "api/api_chat_invite.h"
  12. #include "api/api_premium.h"
  13. #include "base/qthelp_regex.h"
  14. #include "base/qthelp_url.h"
  15. #include "lang/lang_cloud_manager.h"
  16. #include "lang/lang_keys.h"
  17. #include "core/update_checker.h"
  18. #include "core/application.h"
  19. #include "core/click_handler_types.h"
  20. #include "dialogs/ui/dialogs_suggestions.h"
  21. #include "boxes/background_preview_box.h"
  22. #include "ui/boxes/confirm_box.h"
  23. #include "ui/boxes/edit_birthday_box.h"
  24. #include "ui/integration.h"
  25. #include "payments/payments_non_panel_process.h"
  26. #include "boxes/peers/edit_peer_info_box.h"
  27. #include "boxes/share_box.h"
  28. #include "boxes/connection_box.h"
  29. #include "boxes/gift_premium_box.h"
  30. #include "boxes/edit_privacy_box.h"
  31. #include "boxes/premium_preview_box.h"
  32. #include "boxes/sticker_set_box.h"
  33. #include "boxes/star_gift_box.h"
  34. #include "boxes/language_box.h"
  35. #include "passport/passport_form_controller.h"
  36. #include "ui/text/text_utilities.h"
  37. #include "ui/toast/toast.h"
  38. #include "data/components/credits.h"
  39. #include "data/data_birthday.h"
  40. #include "data/data_channel.h"
  41. #include "data/data_document.h"
  42. #include "data/data_session.h"
  43. #include "data/data_user.h"
  44. #include "media/player/media_player_instance.h"
  45. #include "media/view/media_view_open_common.h"
  46. #include "window/window_session_controller.h"
  47. #include "window/window_session_controller_link_info.h"
  48. #include "window/window_controller.h"
  49. #include "window/window_peer_menu.h"
  50. #include "window/themes/window_theme_editor_box.h" // GenerateSlug.
  51. #include "payments/payments_checkout_process.h"
  52. #include "settings/settings_active_sessions.h"
  53. #include "settings/settings_credits.h"
  54. #include "settings/settings_credits_graphics.h"
  55. #include "settings/settings_information.h"
  56. #include "settings/settings_global_ttl.h"
  57. #include "settings/settings_folders.h"
  58. #include "settings/settings_main.h"
  59. #include "settings/settings_privacy_controllers.h"
  60. #include "settings/settings_privacy_security.h"
  61. #include "settings/settings_chat.h"
  62. #include "settings/settings_premium.h"
  63. #include "storage/storage_account.h"
  64. #include "mainwidget.h"
  65. #include "main/main_account.h"
  66. #include "main/main_app_config.h"
  67. #include "main/main_domain.h"
  68. #include "main/main_session.h"
  69. #include "main/main_session_settings.h"
  70. #include "info/info_controller.h"
  71. #include "info/info_memento.h"
  72. #include "inline_bots/bot_attach_web_view.h"
  73. #include "history/history.h"
  74. #include "history/history_item.h"
  75. #include "apiwrap.h"
  76. #include <QtGui/QGuiApplication>
  77. namespace Core {
  78. namespace {
  79. using Match = qthelp::RegularExpressionMatch;
  80. class PersonalChannelController final : public PeerListController {
  81. public:
  82. explicit PersonalChannelController(
  83. not_null<Window::SessionController*> window);
  84. ~PersonalChannelController();
  85. Main::Session &session() const override;
  86. void prepare() override;
  87. void rowClicked(not_null<PeerListRow*> row) override;
  88. [[nodiscard]] rpl::producer<not_null<ChannelData*>> chosen() const;
  89. private:
  90. const not_null<Window::SessionController*> _window;
  91. rpl::event_stream<not_null<ChannelData*>> _chosen;
  92. mtpRequestId _requestId = 0;
  93. };
  94. PersonalChannelController::PersonalChannelController(
  95. not_null<Window::SessionController*> window)
  96. : _window(window) {
  97. }
  98. PersonalChannelController::~PersonalChannelController() {
  99. if (_requestId) {
  100. _window->session().api().request(_requestId).cancel();
  101. }
  102. }
  103. Main::Session &PersonalChannelController::session() const {
  104. return _window->session();
  105. }
  106. void PersonalChannelController::prepare() {
  107. setDescription(object_ptr<Ui::FlatLabel>(
  108. nullptr,
  109. tr::lng_contacts_loading(),
  110. computeListSt().about));
  111. using Flag = MTPchannels_GetAdminedPublicChannels::Flag;
  112. _requestId = _window->session().api().request(
  113. MTPchannels_GetAdminedPublicChannels(
  114. MTP_flags(Flag::f_for_personal))
  115. ).done([=](const MTPmessages_Chats &result) {
  116. _requestId = 0;
  117. setDescription(nullptr);
  118. const auto &chats = result.match([](const auto &data) {
  119. return data.vchats().v;
  120. });
  121. const auto owner = &_window->session().data();
  122. for (const auto &chat : chats) {
  123. if (const auto peer = owner->processChat(chat)) {
  124. const auto rowId = peer->id.value;
  125. const auto channel = peer->asChannel();
  126. if (channel && !delegate()->peerListFindRow(rowId)) {
  127. auto row = std::make_unique<PeerListRow>(peer);
  128. row->setCustomStatus(tr::lng_chat_status_subscribers(
  129. tr::now,
  130. lt_count,
  131. channel->membersCount()));
  132. delegate()->peerListAppendRow(std::move(row));
  133. }
  134. }
  135. }
  136. if (!delegate()->peerListFullRowsCount()) {
  137. auto none = rpl::combine(
  138. tr::lng_settings_channel_no_yet(Ui::Text::WithEntities),
  139. tr::lng_settings_channel_start()
  140. ) | rpl::map([](TextWithEntities &&text, const QString &link) {
  141. return text.append('\n').append(Ui::Text::Link(link));
  142. });
  143. auto label = object_ptr<Ui::FlatLabel>(
  144. nullptr,
  145. std::move(none),
  146. computeListSt().about);
  147. label->setClickHandlerFilter([=](const auto &...) {
  148. _window->showNewChannel();
  149. return false;
  150. });
  151. setDescription(std::move(label));
  152. }
  153. delegate()->peerListRefreshRows();
  154. }).send();
  155. }
  156. void PersonalChannelController::rowClicked(not_null<PeerListRow*> row) {
  157. if (const auto channel = row->peer()->asChannel()) {
  158. _chosen.fire_copy(channel);
  159. }
  160. }
  161. auto PersonalChannelController::chosen() const
  162. -> rpl::producer<not_null<ChannelData*>> {
  163. return _chosen.events();
  164. }
  165. Window::SessionController *ApplyAccountIndex(
  166. not_null<Window::SessionController*> controller,
  167. int accountIndex) {
  168. if (accountIndex <= 0) {
  169. return nullptr;
  170. }
  171. const auto list = Core::App().domain().orderedAccounts();
  172. if (accountIndex > int(list.size())) {
  173. return nullptr;
  174. }
  175. const auto account = list[accountIndex - 1];
  176. if (account == &controller->session().account()) {
  177. return controller;
  178. } else if (const auto window = Core::App().windowFor({ account })) {
  179. if (&window->account() != account) {
  180. Core::App().domain().maybeActivate(account);
  181. if (&window->account() != account) {
  182. return nullptr;
  183. }
  184. }
  185. const auto session = window->sessionController();
  186. if (session) {
  187. return session;
  188. }
  189. }
  190. return nullptr;
  191. }
  192. void SavePersonalChannel(
  193. not_null<Window::SessionController*> window,
  194. ChannelData *channel) {
  195. const auto self = window->session().user();
  196. const auto history = channel
  197. ? channel->owner().history(channel->id).get()
  198. : nullptr;
  199. const auto item = history
  200. ? history->lastServerMessage()
  201. : nullptr;
  202. const auto channelId = channel
  203. ? peerToChannel(channel->id)
  204. : ChannelId();
  205. const auto messageId = item ? item->id : MsgId();
  206. if (self->personalChannelId() != channelId
  207. || (messageId
  208. && self->personalChannelMessageId() != messageId)) {
  209. self->setPersonalChannel(channelId, messageId);
  210. self->session().api().request(MTPaccount_UpdatePersonalChannel(
  211. channel ? channel->inputChannel : MTP_inputChannelEmpty()
  212. )).done(crl::guard(window, [=] {
  213. window->showToast((channel
  214. ? tr::lng_settings_channel_saved
  215. : tr::lng_settings_channel_removed)(tr::now));
  216. })).fail(crl::guard(window, [=](const MTP::Error &error) {
  217. window->showToast(u"Error: "_q + error.type());
  218. })).send();
  219. }
  220. }
  221. bool JoinGroupByHash(
  222. Window::SessionController *controller,
  223. const Match &match,
  224. const QVariant &context) {
  225. if (!controller) {
  226. return false;
  227. }
  228. Api::CheckChatInvite(controller, match->captured(1));
  229. controller->window().activate();
  230. return true;
  231. }
  232. bool JoinFilterBySlug(
  233. Window::SessionController *controller,
  234. const Match &match,
  235. const QVariant &context) {
  236. if (!controller) {
  237. return false;
  238. }
  239. Api::CheckFilterInvite(controller, match->captured(1));
  240. controller->window().activate();
  241. return true;
  242. }
  243. bool ShowStickerSet(
  244. Window::SessionController *controller,
  245. const Match &match,
  246. const QVariant &context) {
  247. if (!controller) {
  248. return false;
  249. }
  250. Core::App().hideMediaView();
  251. controller->show(Box<StickerSetBox>(
  252. controller->uiShow(),
  253. StickerSetIdentifier{ .shortName = match->captured(2) },
  254. (match->captured(1) == "addemoji"
  255. ? Data::StickersType::Emoji
  256. : Data::StickersType::Stickers)));
  257. controller->window().activate();
  258. return true;
  259. }
  260. bool ShowTheme(
  261. Window::SessionController *controller,
  262. const Match &match,
  263. const QVariant &context) {
  264. if (!controller) {
  265. return false;
  266. }
  267. const auto fromMessageId = context.value<ClickHandlerContext>().itemId;
  268. Core::App().hideMediaView();
  269. controller->session().data().cloudThemes().resolve(
  270. &controller->window(),
  271. match->captured(1),
  272. fromMessageId);
  273. controller->window().activate();
  274. return true;
  275. }
  276. void ShowLanguagesBox(Window::SessionController *controller) {
  277. static auto Guard = base::binary_guard();
  278. Guard = LanguageBox::Show(controller);
  279. }
  280. void ShowPhonePrivacyBox(Window::SessionController *controller) {
  281. static auto Guard = base::binary_guard();
  282. auto guard = base::binary_guard();
  283. using Privacy = Api::UserPrivacy;
  284. const auto key = Privacy::Key::PhoneNumber;
  285. controller->session().api().userPrivacy().reload(key);
  286. const auto weak = base::make_weak(controller);
  287. auto shared = std::make_shared<base::binary_guard>(
  288. guard.make_guard());
  289. auto lifetime = std::make_shared<rpl::lifetime>();
  290. controller->session().api().userPrivacy().value(
  291. key
  292. ) | rpl::take(
  293. 1
  294. ) | rpl::start_with_next([=](const Privacy::Rule &value) mutable {
  295. using namespace ::Settings;
  296. const auto show = shared->alive();
  297. if (lifetime) {
  298. base::take(lifetime)->destroy();
  299. }
  300. if (show) {
  301. if (const auto controller = weak.get()) {
  302. controller->show(Box<EditPrivacyBox>(
  303. controller,
  304. std::make_unique<PhoneNumberPrivacyController>(
  305. controller),
  306. value));
  307. }
  308. }
  309. }, *lifetime);
  310. Guard = std::move(guard);
  311. }
  312. bool SetLanguage(
  313. Window::SessionController *controller,
  314. const Match &match,
  315. const QVariant &context) {
  316. if (match->capturedView(1).isEmpty()) {
  317. ShowLanguagesBox(controller);
  318. } else {
  319. const auto languageId = match->captured(2);
  320. Lang::CurrentCloudManager().switchWithWarning(languageId);
  321. }
  322. if (controller) {
  323. controller->window().activate();
  324. }
  325. return true;
  326. }
  327. bool ShareUrl(
  328. Window::SessionController *controller,
  329. const Match &match,
  330. const QVariant &context) {
  331. if (!controller) {
  332. return false;
  333. }
  334. const auto params = url_parse_params(
  335. match->captured(1),
  336. qthelp::UrlParamNameTransform::ToLower);
  337. const auto url = params.value(u"url"_q);
  338. if (url.isEmpty() || url.trimmed().startsWith('@')) {
  339. // Don't allow to insert an inline bot query by share url link.
  340. return false;
  341. }
  342. const auto text = params.value("text");
  343. const auto chosen = [=](not_null<Data::Thread*> thread) {
  344. const auto content = controller->content();
  345. return content->shareUrl(thread, url, text);
  346. };
  347. Window::ShowChooseRecipientBox(controller, chosen);
  348. controller->window().activate();
  349. return true;
  350. }
  351. bool ConfirmPhone(
  352. Window::SessionController *controller,
  353. const Match &match,
  354. const QVariant &context) {
  355. if (!controller) {
  356. return false;
  357. }
  358. const auto params = url_parse_params(
  359. match->captured(1),
  360. qthelp::UrlParamNameTransform::ToLower);
  361. const auto phone = params.value(u"phone"_q);
  362. const auto hash = params.value(u"hash"_q);
  363. if (phone.isEmpty() || hash.isEmpty()) {
  364. return false;
  365. }
  366. controller->session().api().confirmPhone().resolve(
  367. controller,
  368. phone,
  369. hash);
  370. controller->window().activate();
  371. return true;
  372. }
  373. bool ApplySocksProxy(
  374. Window::SessionController *controller,
  375. const Match &match,
  376. const QVariant &context) {
  377. auto params = url_parse_params(
  378. match->captured(1),
  379. qthelp::UrlParamNameTransform::ToLower);
  380. ProxiesBoxController::ShowApplyConfirmation(
  381. controller,
  382. MTP::ProxyData::Type::Socks5,
  383. params);
  384. if (controller) {
  385. controller->window().activate();
  386. }
  387. return true;
  388. }
  389. bool ApplyMtprotoProxy(
  390. Window::SessionController *controller,
  391. const Match &match,
  392. const QVariant &context) {
  393. auto params = url_parse_params(
  394. match->captured(1),
  395. qthelp::UrlParamNameTransform::ToLower);
  396. ProxiesBoxController::ShowApplyConfirmation(
  397. controller,
  398. MTP::ProxyData::Type::Mtproto,
  399. params);
  400. if (controller) {
  401. controller->window().activate();
  402. }
  403. return true;
  404. }
  405. bool ShowPassportForm(
  406. Window::SessionController *controller,
  407. const QMap<QString, QString> &params) {
  408. if (!controller) {
  409. return false;
  410. }
  411. const auto botId = params.value("bot_id", QString()).toULongLong();
  412. const auto scope = params.value("scope", QString());
  413. const auto callback = params.value("callback_url", QString());
  414. const auto publicKey = params.value("public_key", QString());
  415. const auto nonce = params.value(
  416. Passport::NonceNameByScope(scope),
  417. QString());
  418. controller->showPassportForm(Passport::FormRequest(
  419. botId,
  420. scope,
  421. callback,
  422. publicKey,
  423. nonce));
  424. return true;
  425. }
  426. bool ShowPassport(
  427. Window::SessionController *controller,
  428. const Match &match,
  429. const QVariant &context) {
  430. return ShowPassportForm(
  431. controller,
  432. url_parse_params(
  433. match->captured(1),
  434. qthelp::UrlParamNameTransform::ToLower));
  435. }
  436. bool ShowWallPaper(
  437. Window::SessionController *controller,
  438. const Match &match,
  439. const QVariant &context) {
  440. if (!controller) {
  441. return false;
  442. }
  443. const auto params = url_parse_params(
  444. match->captured(1),
  445. qthelp::UrlParamNameTransform::ToLower);
  446. // const auto bg = params.value(u"bg_color"_q);
  447. const auto color = params.value(u"color"_q);
  448. const auto gradient = params.value(u"gradient"_q);
  449. const auto result = BackgroundPreviewBox::Start(
  450. controller,
  451. (!color.isEmpty()
  452. ? color
  453. : !gradient.isEmpty()
  454. ? gradient
  455. : params.value(u"slug"_q)),
  456. params);
  457. controller->window().activate();
  458. return result;
  459. }
  460. [[nodiscard]] ChatAdminRights ParseRequestedAdminRights(
  461. const QString &value) {
  462. auto result = ChatAdminRights();
  463. for (const auto &element : value.split(QRegularExpression(u"[+ ]"_q))) {
  464. if (element == u"change_info"_q) {
  465. result |= ChatAdminRight::ChangeInfo;
  466. } else if (element == u"post_messages"_q) {
  467. result |= ChatAdminRight::PostMessages;
  468. } else if (element == u"edit_messages"_q) {
  469. result |= ChatAdminRight::EditMessages;
  470. } else if (element == u"delete_messages"_q) {
  471. result |= ChatAdminRight::DeleteMessages;
  472. } else if (element == u"restrict_members"_q) {
  473. result |= ChatAdminRight::BanUsers;
  474. } else if (element == u"invite_users"_q) {
  475. result |= ChatAdminRight::InviteByLinkOrAdd;
  476. } else if (element == u"manage_topics"_q) {
  477. result |= ChatAdminRight::ManageTopics;
  478. } else if (element == u"pin_messages"_q) {
  479. result |= ChatAdminRight::PinMessages;
  480. } else if (element == u"promote_members"_q) {
  481. result |= ChatAdminRight::AddAdmins;
  482. } else if (element == u"manage_video_chats"_q) {
  483. result |= ChatAdminRight::ManageCall;
  484. } else if (element == u"anonymous"_q) {
  485. result |= ChatAdminRight::Anonymous;
  486. } else if (element == u"manage_chat"_q) {
  487. result |= ChatAdminRight::Other;
  488. } else {
  489. return {};
  490. }
  491. }
  492. return result;
  493. }
  494. bool ResolveUsernameOrPhone(
  495. Window::SessionController *controller,
  496. const Match &match,
  497. const QVariant &context) {
  498. if (!controller) {
  499. return false;
  500. }
  501. const auto params = url_parse_params(
  502. match->captured(1),
  503. qthelp::UrlParamNameTransform::ToLower);
  504. if (params.contains(u"acc"_q)) {
  505. const auto switched = ApplyAccountIndex(
  506. controller,
  507. params.value(u"acc"_q).toInt());
  508. if (switched) {
  509. controller = switched;
  510. } else {
  511. controller->showToast(u"Could not activate account %1."_q.arg(
  512. params.value(u"acc"_q)));
  513. return false;
  514. }
  515. }
  516. const auto domainParam = params.value(u"domain"_q);
  517. const auto appnameParam = params.value(u"appname"_q);
  518. const auto myContext = context.value<ClickHandlerContext>();
  519. if (domainParam == u"giftcode"_q && !appnameParam.isEmpty()) {
  520. const auto itemId = myContext.itemId;
  521. const auto item = controller->session().data().message(itemId);
  522. const auto fromId = item ? item->from()->id : PeerId();
  523. const auto selfId = controller->session().userPeerId();
  524. const auto toId = !item
  525. ? PeerId()
  526. : (fromId == selfId)
  527. ? item->history()->peer->id
  528. : selfId;
  529. ResolveGiftCode(controller, appnameParam, fromId, toId);
  530. return true;
  531. }
  532. // Fix t.me/s/username links.
  533. const auto webChannelPreviewLink = (domainParam == u"s"_q)
  534. && !appnameParam.isEmpty();
  535. const auto domain = webChannelPreviewLink ? appnameParam : domainParam;
  536. const auto phone = params.value(u"phone"_q);
  537. const auto validDomain = [](const QString &domain) {
  538. return qthelp::regex_match(
  539. u"^[a-zA-Z0-9\\.\\_]+$"_q,
  540. domain,
  541. {}
  542. ).valid();
  543. };
  544. const auto validPhone = [](const QString &phone) {
  545. return qthelp::regex_match(u"^[0-9]+$"_q, phone, {}).valid();
  546. };
  547. if (domain == u"telegrampassport"_q) {
  548. return ShowPassportForm(controller, params);
  549. } else if (!validDomain(domain) && !validPhone(phone)) {
  550. return false;
  551. }
  552. using ResolveType = Window::ResolveType;
  553. auto resolveType = params.contains(u"profile"_q)
  554. ? ResolveType::Profile
  555. : ResolveType::Default;
  556. auto startToken = params.value(u"start"_q);
  557. auto referral = params.value(u"ref"_q);
  558. if (!startToken.isEmpty()) {
  559. resolveType = ResolveType::BotStart;
  560. if (referral.isEmpty()) {
  561. const auto appConfig = &controller->session().appConfig();
  562. const auto &prefixes = appConfig->startRefPrefixes();
  563. for (const auto &prefix : prefixes) {
  564. if (startToken.startsWith(prefix)) {
  565. referral = startToken.mid(prefix.size());
  566. break;
  567. }
  568. }
  569. }
  570. } else if (params.contains(u"startgroup"_q)) {
  571. resolveType = ResolveType::AddToGroup;
  572. startToken = params.value(u"startgroup"_q);
  573. } else if (params.contains(u"startchannel"_q)) {
  574. resolveType = ResolveType::AddToChannel;
  575. } else if (params.contains(u"boost"_q)) {
  576. resolveType = ResolveType::Boost;
  577. }
  578. auto post = ShowAtUnreadMsgId;
  579. auto adminRights = ChatAdminRights();
  580. if (resolveType == ResolveType::AddToGroup
  581. || resolveType == ResolveType::AddToChannel) {
  582. adminRights = ParseRequestedAdminRights(params.value(u"admin"_q));
  583. }
  584. const auto postParam = params.value(u"post"_q);
  585. if (const auto postId = postParam.toInt()) {
  586. post = postId;
  587. }
  588. const auto storyParam = params.value(u"story"_q);
  589. const auto storyId = storyParam.toInt();
  590. const auto appname = webChannelPreviewLink ? QString() : appnameParam;
  591. const auto commentParam = params.value(u"comment"_q);
  592. const auto commentId = commentParam.toInt();
  593. const auto topicParam = params.value(u"topic"_q);
  594. const auto topicId = topicParam.toInt();
  595. const auto threadParam = params.value(u"thread"_q);
  596. const auto threadId = topicId ? topicId : threadParam.toInt();
  597. const auto gameParam = params.value(u"game"_q);
  598. const auto videot = params.value(u"t"_q);
  599. if (!gameParam.isEmpty() && validDomain(gameParam)) {
  600. startToken = gameParam;
  601. resolveType = ResolveType::ShareGame;
  602. }
  603. if (!appname.isEmpty()) {
  604. resolveType = ResolveType::BotApp;
  605. if (startToken.isEmpty() && params.contains(u"startapp"_q)) {
  606. startToken = params.value(u"startapp"_q);
  607. }
  608. }
  609. controller->window().activate();
  610. controller->showPeerByLink(Window::PeerByLinkInfo{
  611. .usernameOrId = domain,
  612. .phone = phone,
  613. .messageId = post,
  614. .storyId = storyId,
  615. .videoTimestamp = (!videot.isEmpty()
  616. ? ParseVideoTimestamp(videot)
  617. : std::optional<TimeId>()),
  618. .text = params.value(u"text"_q),
  619. .repliesInfo = commentId
  620. ? Window::RepliesByLinkInfo{
  621. Window::CommentId{ commentId }
  622. }
  623. : threadId
  624. ? Window::RepliesByLinkInfo{
  625. Window::ThreadId{ threadId }
  626. }
  627. : Window::RepliesByLinkInfo{ v::null },
  628. .resolveType = resolveType,
  629. .referral = referral,
  630. .startToken = startToken,
  631. .startAdminRights = adminRights,
  632. .startAutoSubmit = myContext.botStartAutoSubmit,
  633. .botAppName = (appname.isEmpty() ? postParam : appname),
  634. .botAppForceConfirmation = myContext.mayShowConfirmation,
  635. .botAppFullScreen = (params.value(u"mode"_q) == u"fullscreen"_q),
  636. .attachBotUsername = params.value(u"attach"_q),
  637. .attachBotToggleCommand = (params.contains(u"startattach"_q)
  638. ? params.value(u"startattach"_q)
  639. : (appname.isEmpty() && params.contains(u"startapp"_q))
  640. ? params.value(u"startapp"_q)
  641. : std::optional<QString>()),
  642. .attachBotMainOpen = (appname.isEmpty()
  643. && params.contains(u"startapp"_q)),
  644. .attachBotMainCompact = (appname.isEmpty()
  645. && params.contains(u"startapp"_q)
  646. && (params.value(u"mode"_q) == u"compact"_q)),
  647. .attachBotChooseTypes = InlineBots::ParseChooseTypes(
  648. params.value(u"choose"_q)),
  649. .voicechatHash = (params.contains(u"livestream"_q)
  650. ? std::make_optional(params.value(u"livestream"_q))
  651. : params.contains(u"videochat"_q)
  652. ? std::make_optional(params.value(u"videochat"_q))
  653. : params.contains(u"voicechat"_q)
  654. ? std::make_optional(params.value(u"voicechat"_q))
  655. : std::nullopt),
  656. .clickFromMessageId = myContext.itemId,
  657. .clickFromBotWebviewContext = myContext.botWebviewContext,
  658. });
  659. return true;
  660. }
  661. bool ResolvePrivatePost(
  662. Window::SessionController *controller,
  663. const Match &match,
  664. const QVariant &context) {
  665. if (!controller) {
  666. return false;
  667. }
  668. const auto params = url_parse_params(
  669. match->captured(1),
  670. qthelp::UrlParamNameTransform::ToLower);
  671. const auto channelId = ChannelId(
  672. params.value(u"channel"_q).toULongLong());
  673. const auto msgId = params.value(u"post"_q).toInt();
  674. const auto commentParam = params.value(u"comment"_q);
  675. const auto commentId = commentParam.toInt();
  676. const auto topicParam = params.value(u"topic"_q);
  677. const auto topicId = topicParam.toInt();
  678. const auto threadParam = params.value(u"thread"_q);
  679. const auto threadId = topicId ? topicId : threadParam.toInt();
  680. if (!channelId || (msgId && !IsServerMsgId(msgId))) {
  681. return false;
  682. }
  683. const auto my = context.value<ClickHandlerContext>();
  684. controller->showPeerByLink(Window::PeerByLinkInfo{
  685. .usernameOrId = channelId,
  686. .messageId = msgId,
  687. .repliesInfo = commentId
  688. ? Window::RepliesByLinkInfo{
  689. Window::CommentId{ commentId }
  690. }
  691. : threadId
  692. ? Window::RepliesByLinkInfo{
  693. Window::ThreadId{ threadId }
  694. }
  695. : Window::RepliesByLinkInfo{ v::null },
  696. .clickFromMessageId = my.itemId,
  697. .clickFromBotWebviewContext = my.botWebviewContext,
  698. });
  699. controller->window().activate();
  700. return true;
  701. }
  702. bool ResolveSettings(
  703. Window::SessionController *controller,
  704. const Match &match,
  705. const QVariant &context) {
  706. const auto section = match->captured(1).mid(1).toLower();
  707. const auto type = [&]() -> std::optional<::Settings::Type> {
  708. if (section == u"language"_q) {
  709. ShowLanguagesBox(controller);
  710. return {};
  711. } else if (section == u"phone_privacy"_q) {
  712. ShowPhonePrivacyBox(controller);
  713. return {};
  714. } else if (section == u"devices"_q) {
  715. return ::Settings::Sessions::Id();
  716. } else if (section == u"folders"_q) {
  717. return ::Settings::Folders::Id();
  718. } else if (section == u"privacy"_q) {
  719. return ::Settings::PrivacySecurity::Id();
  720. } else if (section == u"themes"_q) {
  721. return ::Settings::Chat::Id();
  722. } else if (section == u"change_number"_q) {
  723. controller->show(
  724. Ui::MakeInformBox(tr::lng_change_phone_error()));
  725. return {};
  726. } else if (section == u"auto_delete"_q) {
  727. return ::Settings::GlobalTTLId();
  728. } else if (section == u"information"_q) {
  729. return ::Settings::Information::Id();
  730. }
  731. return ::Settings::Main::Id();
  732. }();
  733. if (type.has_value()) {
  734. if (!controller) {
  735. return false;
  736. } else if (*type == ::Settings::Sessions::Id()) {
  737. controller->session().api().authorizations().reload();
  738. }
  739. controller->showSettings(*type);
  740. controller->window().activate();
  741. }
  742. return true;
  743. }
  744. bool HandleUnknown(
  745. Window::SessionController *controller,
  746. const Match &match,
  747. const QVariant &context) {
  748. if (!controller) {
  749. return false;
  750. }
  751. const auto request = match->captured(1);
  752. const auto callback = crl::guard(controller, [=](
  753. TextWithEntities message,
  754. bool updateRequired) {
  755. if (updateRequired) {
  756. const auto callback = [=](Fn<void()> &&close) {
  757. Core::UpdateApplication();
  758. close();
  759. };
  760. controller->show(Ui::MakeConfirmBox({
  761. .text = message,
  762. .confirmed = callback,
  763. .confirmText = tr::lng_menu_update(),
  764. }));
  765. } else {
  766. controller->show(Ui::MakeInformBox(message));
  767. }
  768. });
  769. controller->session().api().requestDeepLinkInfo(request, callback);
  770. return true;
  771. }
  772. bool OpenMediaTimestamp(
  773. Window::SessionController *controller,
  774. const Match &match,
  775. const QVariant &context) {
  776. if (!controller) {
  777. return false;
  778. }
  779. const auto position = match->captured(2).toInt();
  780. if (position < 0) {
  781. return false;
  782. }
  783. const auto base = match->captured(1);
  784. if (base.startsWith(u"doc"_q)) {
  785. const auto parts = base.mid(3).split('_');
  786. const auto documentId = parts.value(0).toULongLong();
  787. const auto itemId = FullMsgId(
  788. PeerId(parts.value(1).toULongLong()),
  789. MsgId(parts.value(2).toLongLong()));
  790. const auto session = &controller->session();
  791. const auto document = session->data().document(documentId);
  792. const auto context = session->data().message(itemId);
  793. const auto time = position * crl::time(1000);
  794. if (document->isVideoFile()) {
  795. controller->window().openInMediaView(Media::View::OpenRequest(
  796. controller,
  797. document,
  798. context,
  799. context ? context->topicRootId() : MsgId(0),
  800. false,
  801. time));
  802. } else if (document->isSong() || document->isVoiceMessage()) {
  803. session->local().setMediaLastPlaybackPosition(documentId, time);
  804. Media::Player::instance()->play({ document, itemId });
  805. }
  806. return true;
  807. }
  808. return false;
  809. }
  810. bool ShowInviteLink(
  811. Window::SessionController *controller,
  812. const Match &match,
  813. const QVariant &context) {
  814. if (!controller) {
  815. return false;
  816. }
  817. const auto base64link = match->captured(1).toLatin1();
  818. const auto link = QString::fromUtf8(QByteArray::fromBase64(base64link));
  819. if (link.isEmpty()) {
  820. return false;
  821. }
  822. QGuiApplication::clipboard()->setText(link);
  823. controller->showToast(tr::lng_group_invite_copied(tr::now));
  824. return true;
  825. }
  826. bool OpenExternalLink(
  827. Window::SessionController *controller,
  828. const Match &match,
  829. const QVariant &context) {
  830. return Ui::Integration::Instance().handleUrlClick(
  831. match->captured(1),
  832. context);
  833. }
  834. bool CopyPeerId(
  835. Window::SessionController *controller,
  836. const Match &match,
  837. const QVariant &context) {
  838. TextUtilities::SetClipboardText({ match->captured(1) });
  839. if (controller) {
  840. controller->showToast(u"ID copied to clipboard."_q);
  841. }
  842. return true;
  843. }
  844. bool ShowSearchTagsPromo(
  845. Window::SessionController *controller,
  846. const Match &match,
  847. const QVariant &context) {
  848. if (!controller) {
  849. return false;
  850. }
  851. ShowPremiumPreviewBox(controller, PremiumFeature::TagsForMessages);
  852. return true;
  853. }
  854. bool ShowEditBirthday(
  855. Window::SessionController *controller,
  856. const Match &match,
  857. const QVariant &context) {
  858. if (!controller) {
  859. return false;
  860. }
  861. const auto user = controller->session().user();
  862. const auto save = [=](Data::Birthday result) {
  863. user->setBirthday(result);
  864. using Flag = MTPaccount_UpdateBirthday::Flag;
  865. using BFlag = MTPDbirthday::Flag;
  866. user->session().api().request(MTPaccount_UpdateBirthday(
  867. MTP_flags(result ? Flag::f_birthday : Flag()),
  868. MTP_birthday(
  869. MTP_flags(result.year() ? BFlag::f_year : BFlag()),
  870. MTP_int(result.day()),
  871. MTP_int(result.month()),
  872. MTP_int(result.year()))
  873. )).done(crl::guard(controller, [=] {
  874. controller->showToast(tr::lng_settings_birthday_saved(tr::now));
  875. })).fail(crl::guard(controller, [=](const MTP::Error &error) {
  876. const auto type = error.type();
  877. controller->showToast(type.startsWith(u"FLOOD_WAIT_"_q)
  878. ? tr::lng_flood_error(tr::now)
  879. : (u"Error: "_q + error.type()));
  880. })).handleFloodErrors().send();
  881. };
  882. controller->show(Box(
  883. Ui::EditBirthdayBox,
  884. user->birthday(),
  885. save));
  886. return true;
  887. }
  888. bool ShowEditBirthdayPrivacy(
  889. Window::SessionController *controller,
  890. const Match &match,
  891. const QVariant &context) {
  892. if (!controller) {
  893. return false;
  894. }
  895. auto syncLifetime = controller->session().api().userPrivacy().value(
  896. Api::UserPrivacy::Key::Birthday
  897. ) | rpl::take(
  898. 1
  899. ) | rpl::start_with_next([=](const Api::UserPrivacy::Rule &value) {
  900. controller->show(Box<EditPrivacyBox>(
  901. controller,
  902. std::make_unique<::Settings::BirthdayPrivacyController>(),
  903. value));
  904. });
  905. return true;
  906. }
  907. bool ShowEditPersonalChannel(
  908. Window::SessionController *controller,
  909. const Match &match,
  910. const QVariant &context) {
  911. if (!controller) {
  912. return false;
  913. }
  914. auto listController = std::make_unique<PersonalChannelController>(
  915. controller);
  916. const auto rawController = listController.get();
  917. auto initBox = [=](not_null<PeerListBox*> box) {
  918. box->setTitle(tr::lng_settings_channel_label());
  919. box->addButton(tr::lng_box_done(), [=] {
  920. box->closeBox();
  921. });
  922. const auto save = [=](ChannelData *channel) {
  923. SavePersonalChannel(controller, channel);
  924. box->closeBox();
  925. };
  926. rawController->chosen(
  927. ) | rpl::start_with_next([=](not_null<ChannelData*> channel) {
  928. save(channel);
  929. }, box->lifetime());
  930. if (controller->session().user()->personalChannelId()) {
  931. box->addLeftButton(tr::lng_settings_channel_remove(), [=] {
  932. save(nullptr);
  933. });
  934. }
  935. };
  936. controller->show(Box<PeerListBox>(
  937. std::move(listController),
  938. std::move(initBox)));
  939. return true;
  940. }
  941. bool ShowCollectiblePhone(
  942. Window::SessionController *controller,
  943. const Match &match,
  944. const QVariant &context) {
  945. if (!controller) {
  946. return false;
  947. }
  948. const auto phone = match->captured(1);
  949. const auto peerId = PeerId(match->captured(2).toULongLong());
  950. controller->resolveCollectible(
  951. peerId,
  952. phone.startsWith('+') ? phone : '+' + phone);
  953. return true;
  954. }
  955. bool ShowCollectibleUsername(
  956. Window::SessionController *controller,
  957. const Match &match,
  958. const QVariant &context) {
  959. if (!controller) {
  960. return false;
  961. }
  962. const auto username = match->captured(1);
  963. const auto peerId = PeerId(match->captured(2).toULongLong());
  964. controller->resolveCollectible(peerId, username);
  965. return true;
  966. }
  967. bool CopyUsernameLink(
  968. Window::SessionController *controller,
  969. const Match &match,
  970. const QVariant &context) {
  971. if (!controller) {
  972. return false;
  973. }
  974. const auto username = match->captured(1);
  975. TextUtilities::SetClipboardText({
  976. controller->session().createInternalLinkFull(username)
  977. });
  978. controller->showToast(tr::lng_username_copied(tr::now));
  979. return true;
  980. }
  981. bool CopyUsername(
  982. Window::SessionController *controller,
  983. const Match &match,
  984. const QVariant &context) {
  985. if (!controller) {
  986. return false;
  987. }
  988. const auto username = match->captured(1);
  989. TextUtilities::SetClipboardText({ '@' + username });
  990. controller->showToast(tr::lng_username_text_copied(tr::now));
  991. return true;
  992. }
  993. bool EditPaidMessagesFee(
  994. Window::SessionController *controller,
  995. const Match &match,
  996. const QVariant &context) {
  997. if (!controller) {
  998. return false;
  999. }
  1000. const auto peerId = PeerId(match->captured(1).toULongLong());
  1001. if (const auto id = peerToChannel(peerId)) {
  1002. const auto channel = controller->session().data().channelLoaded(id);
  1003. if (channel && channel->canEditPermissions()) {
  1004. ShowEditChatPermissions(controller, channel);
  1005. }
  1006. } else {
  1007. controller->show(Box(EditMessagesPrivacyBox, controller));
  1008. }
  1009. return true;
  1010. }
  1011. bool ShowCommonGroups(
  1012. Window::SessionController *controller,
  1013. const Match &match,
  1014. const QVariant &context) {
  1015. if (!controller) {
  1016. return false;
  1017. }
  1018. const auto peerId = PeerId(match->captured(1).toULongLong());
  1019. if (const auto id = peerToUser(peerId)) {
  1020. const auto user = controller->session().data().userLoaded(id);
  1021. if (user) {
  1022. controller->showSection(
  1023. std::make_shared<Info::Memento>(
  1024. user,
  1025. Info::Section::Type::CommonGroups));
  1026. }
  1027. }
  1028. return true;
  1029. }
  1030. bool ShowStarsExamples(
  1031. Window::SessionController *controller,
  1032. const Match &match,
  1033. const QVariant &context) {
  1034. if (!controller) {
  1035. return false;
  1036. }
  1037. controller->show(Dialogs::StarsExamplesBox(controller));
  1038. return true;
  1039. }
  1040. bool ShowPopularAppsAbout(
  1041. Window::SessionController *controller,
  1042. const Match &match,
  1043. const QVariant &context) {
  1044. if (!controller) {
  1045. return false;
  1046. }
  1047. controller->show(Dialogs::PopularAppsAboutBox(controller));
  1048. return true;
  1049. }
  1050. void ExportTestChatTheme(
  1051. not_null<Window::SessionController*> controller,
  1052. not_null<const Data::CloudTheme*> theme) {
  1053. const auto session = &controller->session();
  1054. const auto show = controller->uiShow();
  1055. const auto inputSettings = [&](Data::CloudThemeType type)
  1056. -> std::optional<MTPInputThemeSettings> {
  1057. const auto i = theme->settings.find(type);
  1058. if (i == end(theme->settings)) {
  1059. show->showToast(u"Something went wrong :("_q);
  1060. return std::nullopt;
  1061. }
  1062. const auto &fields = i->second;
  1063. if (!fields.paper
  1064. || !fields.paper->isPattern()
  1065. || fields.paper->backgroundColors().empty()
  1066. || !fields.paper->hasShareUrl()) {
  1067. show->showToast(u"Something went wrong :("_q);
  1068. return std::nullopt;
  1069. }
  1070. const auto &bg = fields.paper->backgroundColors();
  1071. const auto url = fields.paper->shareUrl(&show->session());
  1072. const auto from = url.indexOf("bg/");
  1073. const auto till = url.indexOf("?");
  1074. if (from < 0 || till <= from) {
  1075. show->showToast(u"Bad WallPaper link: "_q + url);
  1076. return std::nullopt;
  1077. }
  1078. using Setting = MTPDinputThemeSettings::Flag;
  1079. using Paper = MTPDwallPaperSettings::Flag;
  1080. const auto color = [](const QColor &color) {
  1081. const auto red = color.red();
  1082. const auto green = color.green();
  1083. const auto blue = color.blue();
  1084. return int(((uint32(red) & 0xFFU) << 16)
  1085. | ((uint32(green) & 0xFFU) << 8)
  1086. | (uint32(blue) & 0xFFU));
  1087. };
  1088. const auto colors = [&](const std::vector<QColor> &colors) {
  1089. auto result = QVector<MTPint>();
  1090. result.reserve(colors.size());
  1091. for (const auto &single : colors) {
  1092. result.push_back(MTP_int(color(single)));
  1093. }
  1094. return result;
  1095. };
  1096. const auto slug = url.mid(from + 3, till - from - 3);
  1097. const auto settings = Setting::f_wallpaper
  1098. | Setting::f_wallpaper_settings
  1099. | (fields.outgoingAccentColor
  1100. ? Setting::f_outbox_accent_color
  1101. : Setting(0))
  1102. | (!fields.outgoingMessagesColors.empty()
  1103. ? Setting::f_message_colors
  1104. : Setting(0));
  1105. const auto papers = Paper::f_background_color
  1106. | Paper::f_intensity
  1107. | (bg.size() > 1
  1108. ? Paper::f_second_background_color
  1109. : Paper(0))
  1110. | (bg.size() > 2
  1111. ? Paper::f_third_background_color
  1112. : Paper(0))
  1113. | (bg.size() > 3
  1114. ? Paper::f_fourth_background_color
  1115. : Paper(0));
  1116. return MTP_inputThemeSettings(
  1117. MTP_flags(settings),
  1118. ((type == Data::CloudThemeType::Dark)
  1119. ? MTP_baseThemeTinted()
  1120. : MTP_baseThemeClassic()),
  1121. MTP_int(color(fields.accentColor)),
  1122. MTP_int(color(fields.outgoingAccentColor.value_or(
  1123. Qt::black))),
  1124. MTP_vector<MTPint>(colors(fields.outgoingMessagesColors)),
  1125. MTP_inputWallPaperSlug(MTP_string(slug)),
  1126. MTP_wallPaperSettings(
  1127. MTP_flags(papers),
  1128. MTP_int(color(bg[0])),
  1129. MTP_int(color(bg.size() > 1 ? bg[1] : Qt::black)),
  1130. MTP_int(color(bg.size() > 2 ? bg[2] : Qt::black)),
  1131. MTP_int(color(bg.size() > 3 ? bg[3] : Qt::black)),
  1132. MTP_int(fields.paper->patternIntensity()),
  1133. MTP_int(0), // rotation
  1134. MTPstring())); // emoticon
  1135. };
  1136. const auto light = inputSettings(Data::CloudThemeType::Light);
  1137. if (!light) {
  1138. return;
  1139. }
  1140. const auto dark = inputSettings(Data::CloudThemeType::Dark);
  1141. if (!dark) {
  1142. return;
  1143. }
  1144. session->api().request(MTPaccount_CreateTheme(
  1145. MTP_flags(MTPaccount_CreateTheme::Flag::f_settings),
  1146. MTP_string(Window::Theme::GenerateSlug()),
  1147. MTP_string(theme->title + " Desktop"),
  1148. MTPInputDocument(),
  1149. MTP_vector<MTPInputThemeSettings>(QVector<MTPInputThemeSettings>{
  1150. *light,
  1151. *dark,
  1152. })
  1153. )).done([=](const MTPTheme &result) {
  1154. const auto slug = Data::CloudTheme::Parse(session, result, true).slug;
  1155. QGuiApplication::clipboard()->setText(
  1156. session->createInternalLinkFull("addtheme/" + slug));
  1157. show->showToast(tr::lng_background_link_copied(tr::now));
  1158. }).fail([=](const MTP::Error &error) {
  1159. show->showToast(u"Error: "_q + error.type());
  1160. }).send();
  1161. }
  1162. bool ResolveTestChatTheme(
  1163. Window::SessionController *controller,
  1164. const Match &match,
  1165. const QVariant &context) {
  1166. if (!controller) {
  1167. return false;
  1168. }
  1169. const auto params = url_parse_params(
  1170. match->captured(1),
  1171. qthelp::UrlParamNameTransform::ToLower);
  1172. if (const auto history = controller->activeChatCurrent().history()) {
  1173. controller->clearCachedChatThemes();
  1174. const auto theme = history->owner().cloudThemes().updateThemeFromLink(
  1175. history->peer->themeEmoji(),
  1176. params);
  1177. if (theme) {
  1178. if (!params["export"].isEmpty()) {
  1179. ExportTestChatTheme(controller, &*theme);
  1180. }
  1181. const auto recache = [&](Data::CloudThemeType type) {
  1182. [[maybe_unused]] auto value = theme->settings.contains(type)
  1183. ? controller->cachedChatThemeValue(
  1184. *theme,
  1185. Data::WallPaper(0),
  1186. type)
  1187. : nullptr;
  1188. };
  1189. recache(Data::CloudThemeType::Dark);
  1190. recache(Data::CloudThemeType::Light);
  1191. }
  1192. }
  1193. return true;
  1194. }
  1195. bool ResolveInvoice(
  1196. Window::SessionController *controller,
  1197. const Match &match,
  1198. const QVariant &context) {
  1199. if (!controller) {
  1200. return false;
  1201. }
  1202. const auto params = url_parse_params(
  1203. match->captured(1),
  1204. qthelp::UrlParamNameTransform::ToLower);
  1205. const auto slug = params.value(u"slug"_q);
  1206. if (slug.isEmpty()) {
  1207. return false;
  1208. }
  1209. const auto window = &controller->window();
  1210. Payments::CheckoutProcess::Start(
  1211. &controller->session(),
  1212. slug,
  1213. crl::guard(window, [=](auto) { window->activate(); }),
  1214. Payments::ProcessNonPanelPaymentFormFactory(controller));
  1215. return true;
  1216. }
  1217. bool ResolvePremiumOffer(
  1218. Window::SessionController *controller,
  1219. const Match &match,
  1220. const QVariant &context) {
  1221. if (!controller) {
  1222. return false;
  1223. }
  1224. const auto params = url_parse_params(
  1225. match->captured(1).mid(1),
  1226. qthelp::UrlParamNameTransform::ToLower);
  1227. const auto refAddition = params.value(u"ref"_q);
  1228. const auto ref = "deeplink"
  1229. + (refAddition.isEmpty() ? QString() : '_' + refAddition);
  1230. ::Settings::ShowPremium(controller, ref);
  1231. controller->window().activate();
  1232. return true;
  1233. }
  1234. bool ResolvePremiumMultigift(
  1235. Window::SessionController *controller,
  1236. const Match &match,
  1237. const QVariant &context) {
  1238. if (!controller) {
  1239. return false;
  1240. }
  1241. Ui::ChooseStarGiftRecipient(controller);
  1242. controller->window().activate();
  1243. return true;
  1244. }
  1245. bool ResolveLoginCode(
  1246. Window::SessionController *controller,
  1247. const Match &match,
  1248. const QVariant &context) {
  1249. const auto loginCode = match->captured(2);
  1250. const auto &domain = Core::App().domain();
  1251. if (loginCode.isEmpty() || (!controller && !domain.started())) {
  1252. return false;
  1253. };
  1254. (controller
  1255. ? controller->session().account()
  1256. : domain.active()).handleLoginCode(loginCode);
  1257. if (controller) {
  1258. controller->window().activate();
  1259. } else if (const auto window = Core::App().activeWindow()) {
  1260. window->activate();
  1261. }
  1262. return true;
  1263. }
  1264. bool ResolveBoost(
  1265. Window::SessionController *controller,
  1266. const Match &match,
  1267. const QVariant &context) {
  1268. if (!controller) {
  1269. return false;
  1270. }
  1271. const auto params = url_parse_params(
  1272. match->captured(1),
  1273. qthelp::UrlParamNameTransform::ToLower);
  1274. const auto domainParam = params.value(u"domain"_q);
  1275. const auto channelParam = params.contains(u"c"_q)
  1276. ? params.value(u"c"_q)
  1277. : params.value(u"channel"_q);
  1278. const auto myContext = context.value<ClickHandlerContext>();
  1279. controller->window().activate();
  1280. controller->showPeerByLink(Window::PeerByLinkInfo{
  1281. .usernameOrId = (!domainParam.isEmpty()
  1282. ? std::variant<QString, ChannelId>(domainParam)
  1283. : ChannelId(BareId(channelParam.toULongLong()))),
  1284. .resolveType = Window::ResolveType::Boost,
  1285. .clickFromMessageId = myContext.itemId,
  1286. });
  1287. return true;
  1288. }
  1289. bool ResolveTopUp(
  1290. Window::SessionController *controller,
  1291. const Match &match,
  1292. const QVariant &context) {
  1293. if (!controller) {
  1294. return false;
  1295. }
  1296. const auto params = url_parse_params(
  1297. match->captured(1),
  1298. qthelp::UrlParamNameTransform::ToLower);
  1299. const auto amount = std::clamp(
  1300. params.value(u"balance"_q).toULongLong(),
  1301. qulonglong(1),
  1302. qulonglong(1'000'000));
  1303. const auto purpose = params.value(u"purpose"_q);
  1304. const auto weak = base::make_weak(controller);
  1305. const auto done = [=](::Settings::SmallBalanceResult result) {
  1306. if (result == ::Settings::SmallBalanceResult::Already) {
  1307. if (const auto strong = weak.get()) {
  1308. const auto filter = [=](const auto &...) {
  1309. strong->showSettings(::Settings::CreditsId());
  1310. return false;
  1311. };
  1312. strong->showToast(Ui::Toast::Config{
  1313. .text = tr::lng_credits_enough(
  1314. tr::now,
  1315. lt_link,
  1316. Ui::Text::Link(
  1317. Ui::Text::Bold(
  1318. tr::lng_credits_enough_link(tr::now))),
  1319. Ui::Text::RichLangValue),
  1320. .filter = filter,
  1321. .duration = 4 * crl::time(1000),
  1322. });
  1323. }
  1324. }
  1325. };
  1326. ::Settings::MaybeRequestBalanceIncrease(
  1327. controller->uiShow(),
  1328. amount,
  1329. ::Settings::SmallBalanceDeepLink{ .purpose = purpose },
  1330. done);
  1331. return true;
  1332. }
  1333. bool ResolveChatLink(
  1334. Window::SessionController *controller,
  1335. const Match &match,
  1336. const QVariant &context) {
  1337. if (!controller) {
  1338. return false;
  1339. }
  1340. const auto myContext = context.value<ClickHandlerContext>();
  1341. const auto slug = match->captured(1);
  1342. controller->window().activate();
  1343. controller->showPeerByLink(Window::PeerByLinkInfo{
  1344. .chatLinkSlug = match->captured(1),
  1345. .clickFromMessageId = myContext.itemId,
  1346. .clickFromBotWebviewContext = myContext.botWebviewContext,
  1347. });
  1348. return true;
  1349. }
  1350. bool ResolveUniqueGift(
  1351. Window::SessionController *controller,
  1352. const Match &match,
  1353. const QVariant &context) {
  1354. if (!controller) {
  1355. return false;
  1356. }
  1357. const auto slug = match->captured(1);
  1358. if (slug.isEmpty()) {
  1359. return false;
  1360. }
  1361. ResolveAndShowUniqueGift(controller->uiShow(), slug);
  1362. return true;
  1363. }
  1364. } // namespace
  1365. const std::vector<LocalUrlHandler> &LocalUrlHandlers() {
  1366. static auto Result = std::vector<LocalUrlHandler>{
  1367. {
  1368. u"^join/?\\?invite=([a-zA-Z0-9\\.\\_\\-]+)(&|$)"_q,
  1369. JoinGroupByHash
  1370. },
  1371. {
  1372. u"^addlist/?\\?slug=([a-zA-Z0-9\\.\\_\\-]+)(&|$)"_q,
  1373. JoinFilterBySlug
  1374. },
  1375. {
  1376. u"^(addstickers|addemoji)/?\\?set=([a-zA-Z0-9\\.\\_]+)(&|$)"_q,
  1377. ShowStickerSet
  1378. },
  1379. {
  1380. u"^addtheme/?\\?slug=([a-zA-Z0-9\\.\\_]+)(&|$)"_q,
  1381. ShowTheme
  1382. },
  1383. {
  1384. u"^setlanguage/?(\\?lang=([a-zA-Z0-9\\.\\_\\-]+))?(&|$)"_q,
  1385. SetLanguage
  1386. },
  1387. {
  1388. u"^msg_url/?\\?(.+)(#|$)"_q,
  1389. ShareUrl
  1390. },
  1391. {
  1392. u"^confirmphone/?\\?(.+)(#|$)"_q,
  1393. ConfirmPhone
  1394. },
  1395. {
  1396. u"^socks/?\\?(.+)(#|$)"_q,
  1397. ApplySocksProxy
  1398. },
  1399. {
  1400. u"^proxy/?\\?(.+)(#|$)"_q,
  1401. ApplyMtprotoProxy
  1402. },
  1403. {
  1404. u"^passport/?\\?(.+)(#|$)"_q,
  1405. ShowPassport
  1406. },
  1407. {
  1408. u"^bg/?\\?(.+)(#|$)"_q,
  1409. ShowWallPaper
  1410. },
  1411. {
  1412. u"^resolve/?\\?(.+)(#|$)"_q,
  1413. ResolveUsernameOrPhone
  1414. },
  1415. {
  1416. u"^privatepost/?\\?(.+)(#|$)"_q,
  1417. ResolvePrivatePost
  1418. },
  1419. {
  1420. u"^settings(/language|/devices|/folders|/privacy|/themes|/change_number|/auto_delete|/information|/edit_profile|/phone_privacy)?$"_q,
  1421. ResolveSettings
  1422. },
  1423. {
  1424. u"^test_chat_theme/?\\?(.+)(#|$)"_q,
  1425. ResolveTestChatTheme,
  1426. },
  1427. {
  1428. u"^invoice/?\\?(.+)(#|$)"_q,
  1429. ResolveInvoice,
  1430. },
  1431. {
  1432. u"^premium_offer/?(\\?.+)?(#|$)"_q,
  1433. ResolvePremiumOffer,
  1434. },
  1435. {
  1436. u"^premium_multigift/?\\?(.+)(#|$)"_q,
  1437. ResolvePremiumMultigift,
  1438. },
  1439. {
  1440. u"^login/?(\\?code=([0-9]+))(&|$)"_q,
  1441. ResolveLoginCode
  1442. },
  1443. {
  1444. u"^boost/?\\?(.+)(#|$)"_q,
  1445. ResolveBoost,
  1446. },
  1447. {
  1448. u"^message/?\\?slug=([a-zA-Z0-9\\.\\_\\-]+)(&|$)"_q,
  1449. ResolveChatLink
  1450. },
  1451. {
  1452. u"^stars_topup/?\\?(.+)(#|$)"_q,
  1453. ResolveTopUp
  1454. },
  1455. {
  1456. u"^nft/?\\?slug=([a-zA-Z0-9\\.\\_\\-]+)(&|$)"_q,
  1457. ResolveUniqueGift
  1458. },
  1459. {
  1460. u"^([^\\?]+)(\\?|#|$)"_q,
  1461. HandleUnknown
  1462. },
  1463. };
  1464. return Result;
  1465. }
  1466. const std::vector<LocalUrlHandler> &InternalUrlHandlers() {
  1467. static auto Result = std::vector<LocalUrlHandler>{
  1468. {
  1469. u"^media_timestamp/?\\?base=([a-zA-Z0-9\\.\\_\\-]+)&t=(\\d+)(&|$)"_q,
  1470. OpenMediaTimestamp
  1471. },
  1472. {
  1473. u"^show_invite_link/?\\?link=([a-zA-Z0-9_\\+\\/\\=\\-]+)(&|$)"_q,
  1474. ShowInviteLink
  1475. },
  1476. {
  1477. u"^url:(.+)$"_q,
  1478. OpenExternalLink
  1479. },
  1480. {
  1481. u"^copy:(.+)$"_q,
  1482. CopyPeerId
  1483. },
  1484. {
  1485. u"^about_tags$"_q,
  1486. ShowSearchTagsPromo
  1487. },
  1488. {
  1489. u"^edit_birthday$"_q,
  1490. ShowEditBirthday,
  1491. },
  1492. {
  1493. u"^edit_privacy_birthday$"_q,
  1494. ShowEditBirthdayPrivacy,
  1495. },
  1496. {
  1497. u"^edit_personal_channel$"_q,
  1498. ShowEditPersonalChannel,
  1499. },
  1500. {
  1501. u"^collectible_phone/([\\+0-9\\-\\s]+)@([0-9]+)$"_q,
  1502. ShowCollectiblePhone,
  1503. },
  1504. {
  1505. u"^collectible_username/([a-zA-Z0-9\\-\\_\\.]+)@([0-9]+)$"_q,
  1506. ShowCollectibleUsername,
  1507. },
  1508. {
  1509. u"^username_link/([a-zA-Z0-9\\-\\_\\.]+)@([0-9]+)$"_q,
  1510. CopyUsernameLink,
  1511. },
  1512. {
  1513. u"^username_regular/([a-zA-Z0-9\\-\\_\\.]+)@([0-9]+)$"_q,
  1514. CopyUsername,
  1515. },
  1516. {
  1517. u"^edit_paid_messages_fee/([0-9]+)$"_q,
  1518. EditPaidMessagesFee,
  1519. },
  1520. {
  1521. u"^common_groups/([0-9]+)$"_q,
  1522. ShowCommonGroups,
  1523. },
  1524. {
  1525. u"^stars_examples$"_q,
  1526. ShowStarsExamples,
  1527. },
  1528. {
  1529. u"^about_popular_apps$"_q,
  1530. ShowPopularAppsAbout,
  1531. },
  1532. };
  1533. return Result;
  1534. }
  1535. QString TryConvertUrlToLocal(QString url) {
  1536. if (url.size() > 8192) {
  1537. url = url.mid(0, 8192);
  1538. }
  1539. using namespace qthelp;
  1540. auto matchOptions = RegExOption::CaseInsensitive;
  1541. auto tonsiteMatch = (url.indexOf(u".ton") >= 0)
  1542. ? regex_match(u"^(https?://)?[^/@:]+\\.ton($|/)"_q, url, matchOptions)
  1543. : RegularExpressionMatch(QRegularExpressionMatch());
  1544. if (tonsiteMatch) {
  1545. const auto protocol = tonsiteMatch->captured(1);
  1546. return u"tonsite://"_q + url.mid(protocol.size());
  1547. }
  1548. auto subdomainMatch = regex_match(u"^(https?://)?([a-zA-Z0-9\\_]+)\\.t\\.me(/\\d+)?/?(\\?.+)?"_q, url, matchOptions);
  1549. if (subdomainMatch) {
  1550. const auto name = subdomainMatch->captured(2);
  1551. if (name.size() > 1 && name != "www") {
  1552. const auto result = TryConvertUrlToLocal(
  1553. subdomainMatch->captured(1)
  1554. + "t.me/"
  1555. + name
  1556. + subdomainMatch->captured(3)
  1557. + subdomainMatch->captured(4));
  1558. return result.startsWith("tg://resolve?domain=")
  1559. ? result
  1560. : url;
  1561. }
  1562. }
  1563. auto telegramMeMatch = regex_match(u"^(https?://)?(www\\.)?(telegram\\.(me|dog)|t\\.me)/(.+)$"_q, url, matchOptions);
  1564. if (telegramMeMatch) {
  1565. const auto query = telegramMeMatch->capturedView(5);
  1566. if (const auto phoneMatch = regex_match(u"^\\+([0-9]+)(\\?|$)"_q, query, matchOptions)) {
  1567. const auto params = query.mid(phoneMatch->captured(0).size()).toString();
  1568. return u"tg://resolve?phone="_q + phoneMatch->captured(1) + (params.isEmpty() ? QString() : '&' + params);
  1569. } else if (const auto joinChatMatch = regex_match(u"^(joinchat/|\\+|\\%20)([a-zA-Z0-9\\.\\_\\-]+)(\\?|$)"_q, query, matchOptions)) {
  1570. return u"tg://join?invite="_q + url_encode(joinChatMatch->captured(2));
  1571. } else if (const auto joinFilterMatch = regex_match(u"^(addlist/)([a-zA-Z0-9\\.\\_\\-]+)(\\?|$)"_q, query, matchOptions)) {
  1572. return u"tg://addlist?slug="_q + url_encode(joinFilterMatch->captured(2));
  1573. } else if (const auto stickerSetMatch = regex_match(u"^(addstickers|addemoji)/([a-zA-Z0-9\\.\\_]+)(\\?|$)"_q, query, matchOptions)) {
  1574. return u"tg://"_q + stickerSetMatch->captured(1) + "?set=" + url_encode(stickerSetMatch->captured(2));
  1575. } else if (const auto themeMatch = regex_match(u"^addtheme/([a-zA-Z0-9\\.\\_]+)(\\?|$)"_q, query, matchOptions)) {
  1576. return u"tg://addtheme?slug="_q + url_encode(themeMatch->captured(1));
  1577. } else if (const auto languageMatch = regex_match(u"^setlanguage/([a-zA-Z0-9\\.\\_\\-]+)(\\?|$)"_q, query, matchOptions)) {
  1578. return u"tg://setlanguage?lang="_q + url_encode(languageMatch->captured(1));
  1579. } else if (const auto shareUrlMatch = regex_match(u"^share/url/?\\?(.+)$"_q, query, matchOptions)) {
  1580. return u"tg://msg_url?"_q + shareUrlMatch->captured(1);
  1581. } else if (const auto confirmPhoneMatch = regex_match(u"^confirmphone/?\\?(.+)"_q, query, matchOptions)) {
  1582. return u"tg://confirmphone?"_q + confirmPhoneMatch->captured(1);
  1583. } else if (const auto ivMatch = regex_match(u"^iv/?\\?(.+)(#|$)"_q, query, matchOptions)) {
  1584. //
  1585. // We need to show our t.me page, not the url directly.
  1586. //
  1587. //auto params = url_parse_params(ivMatch->captured(1), UrlParamNameTransform::ToLower);
  1588. //auto previewedUrl = params.value(u"url"_q);
  1589. //if (previewedUrl.startsWith(u"http://"_q, Qt::CaseInsensitive)
  1590. // || previewedUrl.startsWith(u"https://"_q, Qt::CaseInsensitive)) {
  1591. // return previewedUrl;
  1592. //}
  1593. return url;
  1594. } else if (const auto socksMatch = regex_match(u"^socks/?\\?(.+)(#|$)"_q, query, matchOptions)) {
  1595. return u"tg://socks?"_q + socksMatch->captured(1);
  1596. } else if (const auto proxyMatch = regex_match(u"^proxy/?\\?(.+)(#|$)"_q, query, matchOptions)) {
  1597. return u"tg://proxy?"_q + proxyMatch->captured(1);
  1598. } else if (const auto invoiceMatch = regex_match(u"^(invoice/|\\$)([a-zA-Z0-9_\\-]+)(\\?|#|$)"_q, query, matchOptions)) {
  1599. return u"tg://invoice?slug="_q + invoiceMatch->captured(2);
  1600. } else if (const auto bgMatch = regex_match(u"^bg/([a-zA-Z0-9\\.\\_\\-\\~]+)(\\?(.+)?)?$"_q, query, matchOptions)) {
  1601. const auto params = bgMatch->captured(3);
  1602. const auto bg = bgMatch->captured(1);
  1603. const auto type = regex_match(u"^[a-fA-F0-9]{6}^"_q, bg)
  1604. ? "color"
  1605. : (regex_match(u"^[a-fA-F0-9]{6}\\-[a-fA-F0-9]{6}$"_q, bg)
  1606. || regex_match(u"^[a-fA-F0-9]{6}(\\~[a-fA-F0-9]{6}){1,3}$"_q, bg))
  1607. ? "gradient"
  1608. : "slug";
  1609. return u"tg://bg?"_q + type + '=' + bg + (params.isEmpty() ? QString() : '&' + params);
  1610. } else if (const auto chatlinkMatch = regex_match(u"^m/([a-zA-Z0-9\\.\\_\\-]+)(\\?|$)"_q, query, matchOptions)) {
  1611. const auto slug = chatlinkMatch->captured(1);
  1612. return u"tg://message?slug="_q + slug;
  1613. } else if (const auto nftMatch = regex_match(u"^nft/([a-zA-Z0-9\\.\\_\\-]+)(\\?|$)"_q, query, matchOptions)) {
  1614. const auto slug = nftMatch->captured(1);
  1615. return u"tg://nft?slug="_q + slug;
  1616. } else if (const auto privateMatch = regex_match(u"^"
  1617. "c/(\\-?\\d+)"
  1618. "("
  1619. "/?\\?|"
  1620. "/?$|"
  1621. "/\\d+/?(\\?|$)|"
  1622. "/\\d+/\\d+/?(\\?|$)"
  1623. ")"_q, query, matchOptions)) {
  1624. const auto channel = privateMatch->captured(1);
  1625. const auto params = query.mid(privateMatch->captured(0).size()).toString();
  1626. if (params.indexOf("boost", 0, Qt::CaseInsensitive) >= 0
  1627. && params.toLower().split('&').contains(u"boost"_q)) {
  1628. return u"tg://boost?channel="_q + channel;
  1629. }
  1630. const auto base = u"tg://privatepost?channel="_q + channel;
  1631. auto added = QString();
  1632. if (const auto threadPostMatch = regex_match(u"^/(\\d+)/(\\d+)(/?\\?|/?$)"_q, privateMatch->captured(2))) {
  1633. added = u"&topic=%1&post=%2"_q.arg(threadPostMatch->captured(1), threadPostMatch->captured(2));
  1634. } else if (const auto postMatch = regex_match(u"^/(\\d+)(/?\\?|/?$)"_q, privateMatch->captured(2))) {
  1635. added = u"&post="_q + postMatch->captured(1);
  1636. }
  1637. return base + added + (params.isEmpty() ? QString() : '&' + params);
  1638. } else if (const auto usernameMatch = regex_match(u"^"
  1639. "([a-zA-Z0-9\\.\\_]+)"
  1640. "("
  1641. "/?\\?|"
  1642. "/?$|"
  1643. "/[a-zA-Z0-9\\.\\_\\-]+/?(\\?|$)|"
  1644. "/\\d+/?(\\?|$)|"
  1645. "/s/\\d+/?(\\?|$)|"
  1646. "/\\d+/\\d+/?(\\?|$)"
  1647. ")"_q, query, matchOptions)) {
  1648. const auto domain = usernameMatch->captured(1);
  1649. const auto params = query.mid(usernameMatch->captured(0).size()).toString();
  1650. if (params.indexOf("boost", 0, Qt::CaseInsensitive) >= 0
  1651. && params.toLower().split('&').contains(u"boost"_q)) {
  1652. return u"tg://boost?domain="_q + domain;
  1653. } else if (domain == u"boost"_q) {
  1654. if (const auto domainMatch = regex_match(u"^/([a-zA-Z0-9\\.\\_]+)(/?\\?|/?$)"_q, usernameMatch->captured(2))) {
  1655. return u"tg://boost?domain="_q + domainMatch->captured(1);
  1656. } else if (params.indexOf("c=", 0, Qt::CaseInsensitive) >= 0) {
  1657. return u"tg://boost?"_q + params;
  1658. }
  1659. }
  1660. const auto base = u"tg://resolve?domain="_q + url_encode(usernameMatch->captured(1));
  1661. auto added = QString();
  1662. if (const auto threadPostMatch = regex_match(u"^/(\\d+)/(\\d+)(/?\\?|/?$)"_q, usernameMatch->captured(2))) {
  1663. added = u"&topic=%1&post=%2"_q.arg(threadPostMatch->captured(1), threadPostMatch->captured(2));
  1664. } else if (const auto postMatch = regex_match(u"^/(\\d+)(/?\\?|/?$)"_q, usernameMatch->captured(2))) {
  1665. added = u"&post="_q + postMatch->captured(1);
  1666. } else if (const auto storyMatch = regex_match(u"^/s/(\\d+)(/?\\?|/?$)"_q, usernameMatch->captured(2))) {
  1667. added = u"&story="_q + storyMatch->captured(1);
  1668. } else if (const auto appNameMatch = regex_match(u"^/([a-zA-Z0-9\\.\\_\\-]+)(/?\\?|/?$)"_q, usernameMatch->captured(2))) {
  1669. added = u"&appname="_q + appNameMatch->captured(1);
  1670. }
  1671. return base + added + (params.isEmpty() ? QString() : '&' + params);
  1672. }
  1673. }
  1674. return url;
  1675. }
  1676. bool InternalPassportLink(const QString &url) {
  1677. const auto urlTrimmed = url.trimmed();
  1678. if (!urlTrimmed.startsWith(u"tg://"_q, Qt::CaseInsensitive)) {
  1679. return false;
  1680. }
  1681. const auto command = base::StringViewMid(urlTrimmed, u"tg://"_q.size());
  1682. using namespace qthelp;
  1683. const auto matchOptions = RegExOption::CaseInsensitive;
  1684. const auto authMatch = regex_match(
  1685. u"^passport/?\\?(.+)(#|$)"_q,
  1686. command,
  1687. matchOptions);
  1688. const auto usernameMatch = regex_match(
  1689. u"^resolve/?\\?(.+)(#|$)"_q,
  1690. command,
  1691. matchOptions);
  1692. const auto usernameValue = usernameMatch->hasMatch()
  1693. ? url_parse_params(
  1694. usernameMatch->captured(1),
  1695. UrlParamNameTransform::ToLower).value(u"domain"_q)
  1696. : QString();
  1697. const auto authLegacy = (usernameValue == u"telegrampassport"_q);
  1698. return authMatch->hasMatch() || authLegacy;
  1699. }
  1700. bool StartUrlRequiresActivate(const QString &url) {
  1701. return Core::App().passcodeLocked()
  1702. ? true
  1703. : !InternalPassportLink(url);
  1704. }
  1705. void ResolveAndShowUniqueGift(
  1706. std::shared_ptr<ChatHelpers::Show> show,
  1707. const QString &slug,
  1708. ::Settings::CreditsEntryBoxStyleOverrides st) {
  1709. struct Request {
  1710. base::weak_ptr<Main::Session> weak;
  1711. QString slug;
  1712. mtpRequestId id = 0;
  1713. };
  1714. static auto request = Request();
  1715. const auto session = &show->session();
  1716. if (request.weak.get() == session && request.slug == slug) {
  1717. return;
  1718. } else if (const auto strong = request.weak.get()) {
  1719. strong->api().request(request.id).cancel();
  1720. }
  1721. request.weak = session;
  1722. request.slug = slug;
  1723. const auto clear = [=] {
  1724. if (request.weak.get() == session && request.slug == slug) {
  1725. request = {};
  1726. }
  1727. };
  1728. request.id = session->api().request(
  1729. MTPpayments_GetUniqueStarGift(MTP_string(slug))
  1730. ).done([=](const MTPpayments_UniqueStarGift &result) {
  1731. clear();
  1732. const auto &data = result.data();
  1733. session->data().processUsers(data.vusers());
  1734. if (const auto gift = Api::FromTL(session, data.vgift())) {
  1735. using namespace ::Settings;
  1736. show->show(Box(GlobalStarGiftBox, show, *gift, st));
  1737. }
  1738. }).fail([=](const MTP::Error &error) {
  1739. clear();
  1740. show->showToast(u"Error: "_q + error.type());
  1741. }).send();
  1742. }
  1743. void ResolveAndShowUniqueGift(
  1744. std::shared_ptr<ChatHelpers::Show> show,
  1745. const QString &slug) {
  1746. ResolveAndShowUniqueGift(std::move(show), slug, {});
  1747. }
  1748. TimeId ParseVideoTimestamp(QStringView value) {
  1749. const auto kExp = u"^(?:(\\d+)h)?(?:(\\d+)m)?(?:(\\d+)s)?$"_q;
  1750. const auto m = QRegularExpression(kExp).match(value);
  1751. return m.hasMatch()
  1752. ? (m.capturedView(1).toInt() * 3600
  1753. + m.capturedView(2).toInt() * 60
  1754. + m.capturedView(3).toInt())
  1755. : value.toInt();
  1756. }
  1757. } // namespace Core