api_bot.cpp 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525
  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 "api/api_bot.h"
  8. #include "apiwrap.h"
  9. #include "api/api_cloud_password.h"
  10. #include "api/api_send_progress.h"
  11. #include "boxes/share_box.h"
  12. #include "boxes/passcode_box.h"
  13. #include "boxes/url_auth_box.h"
  14. #include "boxes/peers/choose_peer_box.h"
  15. #include "lang/lang_keys.h"
  16. #include "chat_helpers/bot_command.h"
  17. #include "core/core_cloud_password.h"
  18. #include "core/click_handler_types.h"
  19. #include "data/data_changes.h"
  20. #include "data/data_peer.h"
  21. #include "data/data_poll.h"
  22. #include "data/data_user.h"
  23. #include "data/data_session.h"
  24. #include "history/history.h"
  25. #include "history/history_item.h"
  26. #include "history/history_item_components.h"
  27. #include "inline_bots/bot_attach_web_view.h"
  28. #include "payments/payments_checkout_process.h"
  29. #include "payments/payments_non_panel_process.h"
  30. #include "main/main_session.h"
  31. #include "mainwidget.h"
  32. #include "mainwindow.h"
  33. #include "window/window_session_controller.h"
  34. #include "window/window_peer_menu.h"
  35. #include "ui/boxes/confirm_box.h"
  36. #include "ui/toast/toast.h"
  37. #include "ui/layers/generic_box.h"
  38. #include "ui/text/text_utilities.h"
  39. #include <QtGui/QGuiApplication>
  40. #include <QtGui/QClipboard>
  41. namespace Api {
  42. namespace {
  43. void SendBotCallbackData(
  44. not_null<Window::SessionController*> controller,
  45. not_null<HistoryItem*> item,
  46. int row,
  47. int column,
  48. std::optional<Core::CloudPasswordResult> password,
  49. Fn<void()> done = nullptr,
  50. Fn<void(const QString &)> handleError = nullptr) {
  51. if (!item->isRegular()) {
  52. return;
  53. }
  54. const auto history = item->history();
  55. const auto session = &history->session();
  56. const auto owner = &history->owner();
  57. const auto api = &session->api();
  58. const auto bot = item->getMessageBot();
  59. const auto fullId = item->fullId();
  60. const auto getButton = [=] {
  61. return HistoryMessageMarkupButton::Get(owner, fullId, row, column);
  62. };
  63. const auto button = getButton();
  64. if (!button || button->requestId) {
  65. return;
  66. }
  67. using ButtonType = HistoryMessageMarkupButton::Type;
  68. const auto isGame = (button->type == ButtonType::Game);
  69. auto flags = MTPmessages_GetBotCallbackAnswer::Flags(0);
  70. QByteArray sendData;
  71. if (isGame) {
  72. flags |= MTPmessages_GetBotCallbackAnswer::Flag::f_game;
  73. } else if (button->type == ButtonType::Callback
  74. || button->type == ButtonType::CallbackWithPassword) {
  75. flags |= MTPmessages_GetBotCallbackAnswer::Flag::f_data;
  76. sendData = button->data;
  77. }
  78. const auto withPassword = password.has_value();
  79. if (withPassword) {
  80. flags |= MTPmessages_GetBotCallbackAnswer::Flag::f_password;
  81. }
  82. const auto weak = base::make_weak(controller);
  83. const auto show = controller->uiShow();
  84. button->requestId = api->request(MTPmessages_GetBotCallbackAnswer(
  85. MTP_flags(flags),
  86. history->peer->input,
  87. MTP_int(item->id),
  88. MTP_bytes(sendData),
  89. password ? password->result : MTP_inputCheckPasswordEmpty()
  90. )).done([=](const MTPmessages_BotCallbackAnswer &result) {
  91. const auto guard = gsl::finally([&] {
  92. if (done) {
  93. done();
  94. }
  95. });
  96. const auto item = owner->message(fullId);
  97. if (!item) {
  98. return;
  99. }
  100. if (const auto button = getButton()) {
  101. button->requestId = 0;
  102. owner->requestItemRepaint(item);
  103. }
  104. const auto &data = result.data();
  105. const auto message = data.vmessage()
  106. ? qs(*data.vmessage())
  107. : QString();
  108. const auto link = data.vurl() ? qs(*data.vurl()) : QString();
  109. const auto showAlert = data.is_alert();
  110. if (!message.isEmpty()) {
  111. if (!show->valid()) {
  112. return;
  113. } else if (showAlert) {
  114. show->showBox(Ui::MakeInformBox(message));
  115. } else {
  116. if (withPassword) {
  117. show->hideLayer();
  118. }
  119. show->showToast(message);
  120. }
  121. } else if (!link.isEmpty()) {
  122. if (!isGame) {
  123. UrlClickHandler::Open(link);
  124. return;
  125. }
  126. BotGameUrlClickHandler(bot, link).onClick({
  127. Qt::LeftButton,
  128. QVariant::fromValue(ClickHandlerContext{
  129. .itemId = item->fullId(),
  130. .sessionWindow = weak,
  131. }),
  132. });
  133. session->sendProgressManager().update(
  134. history,
  135. Api::SendProgressType::PlayGame);
  136. } else if (withPassword) {
  137. show->hideLayer();
  138. }
  139. }).fail([=](const MTP::Error &error) {
  140. const auto guard = gsl::finally([&] {
  141. if (handleError) {
  142. handleError(error.type());
  143. }
  144. });
  145. const auto item = owner->message(fullId);
  146. if (!item) {
  147. return;
  148. }
  149. // Show error?
  150. if (const auto button = getButton()) {
  151. button->requestId = 0;
  152. owner->requestItemRepaint(item);
  153. }
  154. }).send();
  155. session->changes().messageUpdated(
  156. item,
  157. Data::MessageUpdate::Flag::BotCallbackSent
  158. );
  159. }
  160. void HideSingleUseKeyboard(
  161. not_null<Window::SessionController*> controller,
  162. not_null<HistoryItem*> item) {
  163. controller->content()->hideSingleUseKeyboard(item->fullId());
  164. }
  165. } // namespace
  166. void SendBotCallbackData(
  167. not_null<Window::SessionController*> controller,
  168. not_null<HistoryItem*> item,
  169. int row,
  170. int column) {
  171. SendBotCallbackData(controller, item, row, column, std::nullopt);
  172. }
  173. void SendBotCallbackDataWithPassword(
  174. not_null<Window::SessionController*> controller,
  175. not_null<HistoryItem*> item,
  176. int row,
  177. int column) {
  178. if (!item->isRegular()) {
  179. return;
  180. }
  181. const auto history = item->history();
  182. const auto session = &history->session();
  183. const auto owner = &history->owner();
  184. const auto api = &session->api();
  185. const auto fullId = item->fullId();
  186. const auto getButton = [=] {
  187. return HistoryMessageMarkupButton::Get(
  188. owner,
  189. fullId,
  190. row,
  191. column);
  192. };
  193. const auto button = getButton();
  194. if (!button || button->requestId) {
  195. return;
  196. }
  197. api->cloudPassword().reload();
  198. const auto weak = base::make_weak(controller);
  199. const auto show = controller->uiShow();
  200. SendBotCallbackData(controller, item, row, column, {}, {}, [=](
  201. const QString &error) {
  202. auto box = PrePasswordErrorBox(
  203. error,
  204. session,
  205. tr::lng_bots_password_confirm_check_about(
  206. tr::now,
  207. Ui::Text::WithEntities));
  208. if (box) {
  209. show->showBox(std::move(box), Ui::LayerOption::CloseOther);
  210. } else {
  211. auto lifetime = std::make_shared<rpl::lifetime>();
  212. button->requestId = -1;
  213. api->cloudPassword().state(
  214. ) | rpl::take(
  215. 1
  216. ) | rpl::start_with_next([=](const Core::CloudPasswordState &state) mutable {
  217. if (lifetime) {
  218. base::take(lifetime)->destroy();
  219. }
  220. if (const auto button = getButton()) {
  221. if (button->requestId == -1) {
  222. button->requestId = 0;
  223. }
  224. } else {
  225. return;
  226. }
  227. auto fields = PasscodeBox::CloudFields::From(state);
  228. fields.customTitle = tr::lng_bots_password_confirm_title();
  229. fields.customDescription
  230. = tr::lng_bots_password_confirm_description(tr::now);
  231. fields.customSubmitButton = tr::lng_passcode_submit();
  232. fields.customCheckCallback = [=](
  233. const Core::CloudPasswordResult &result,
  234. QPointer<PasscodeBox> box) {
  235. if (const auto button = getButton()) {
  236. if (button->requestId) {
  237. return;
  238. }
  239. } else {
  240. return;
  241. }
  242. if (const auto item = owner->message(fullId)) {
  243. const auto strongController = weak.get();
  244. if (!strongController) {
  245. return;
  246. }
  247. SendBotCallbackData(strongController, item, row, column, result, [=] {
  248. if (box) {
  249. box->closeBox();
  250. }
  251. }, [=](const QString &error) {
  252. if (box) {
  253. box->handleCustomCheckError(error);
  254. }
  255. });
  256. }
  257. };
  258. auto object = Box<PasscodeBox>(session, fields);
  259. show->showBox(std::move(object), Ui::LayerOption::CloseOther);
  260. }, *lifetime);
  261. }
  262. });
  263. }
  264. bool SwitchInlineBotButtonReceived(
  265. not_null<Window::SessionController*> controller,
  266. const QByteArray &queryWithPeerTypes,
  267. UserData *samePeerBot,
  268. MsgId samePeerReplyTo) {
  269. return controller->content()->notify_switchInlineBotButtonReceived(
  270. QString::fromUtf8(queryWithPeerTypes),
  271. samePeerBot,
  272. samePeerReplyTo);
  273. }
  274. void ActivateBotCommand(ClickHandlerContext context, int row, int column) {
  275. const auto strong = context.sessionWindow.get();
  276. if (!strong) {
  277. return;
  278. }
  279. const auto controller = not_null{ strong };
  280. const auto item = controller->session().data().message(context.itemId);
  281. if (!item) {
  282. return;
  283. }
  284. const auto button = HistoryMessageMarkupButton::Get(
  285. &item->history()->owner(),
  286. item->fullId(),
  287. row,
  288. column);
  289. if (!button) {
  290. return;
  291. }
  292. using ButtonType = HistoryMessageMarkupButton::Type;
  293. switch (button->type) {
  294. case ButtonType::Default: {
  295. // Copy string before passing it to the sending method
  296. // because the original button can be destroyed inside.
  297. const auto replyTo = item->isRegular()
  298. ? item->fullId()
  299. : FullMsgId();
  300. controller->content()->sendBotCommand({
  301. .peer = item->history()->peer,
  302. .command = QString(button->text),
  303. .context = item->fullId(),
  304. .replyTo = { replyTo },
  305. });
  306. } break;
  307. case ButtonType::Callback:
  308. case ButtonType::Game: {
  309. SendBotCallbackData(controller, item, row, column);
  310. } break;
  311. case ButtonType::CallbackWithPassword: {
  312. SendBotCallbackDataWithPassword(controller, item, row, column);
  313. } break;
  314. case ButtonType::Buy: {
  315. Payments::CheckoutProcess::Start(
  316. item,
  317. Payments::Mode::Payment,
  318. crl::guard(controller, [=](auto) {
  319. controller->widget()->activate();
  320. }),
  321. Payments::ProcessNonPanelPaymentFormFactory(controller, item));
  322. } break;
  323. case ButtonType::Url: {
  324. auto url = QString::fromUtf8(button->data);
  325. auto skipConfirmation = false;
  326. if (const auto bot = item->getMessageBot()) {
  327. if (bot->isVerified()) {
  328. skipConfirmation = true;
  329. }
  330. }
  331. const auto variant = QVariant::fromValue(context);
  332. if (skipConfirmation) {
  333. UrlClickHandler::Open(url, variant);
  334. } else {
  335. HiddenUrlClickHandler::Open(url, variant);
  336. }
  337. } break;
  338. case ButtonType::RequestLocation: {
  339. HideSingleUseKeyboard(controller, item);
  340. controller->show(
  341. Ui::MakeInformBox(tr::lng_bot_share_location_unavailable()));
  342. } break;
  343. case ButtonType::RequestPhone: {
  344. HideSingleUseKeyboard(controller, item);
  345. const auto itemId = item->fullId();
  346. const auto topicRootId = item->topicRootId();
  347. const auto history = item->history();
  348. controller->show(Ui::MakeConfirmBox({
  349. .text = tr::lng_bot_share_phone(),
  350. .confirmed = [=] {
  351. controller->showPeerHistory(
  352. history,
  353. Window::SectionShow::Way::Forward,
  354. ShowAtTheEndMsgId);
  355. auto action = Api::SendAction(history);
  356. action.clearDraft = false;
  357. action.replyTo = {
  358. .messageId = itemId,
  359. .topicRootId = topicRootId,
  360. };
  361. history->session().api().shareContact(
  362. history->session().user(),
  363. action);
  364. },
  365. .confirmText = tr::lng_bot_share_phone_confirm(),
  366. }));
  367. } break;
  368. case ButtonType::RequestPoll: {
  369. HideSingleUseKeyboard(controller, item);
  370. auto chosen = PollData::Flags();
  371. auto disabled = PollData::Flags();
  372. if (!button->data.isEmpty()) {
  373. disabled |= PollData::Flag::Quiz;
  374. if (button->data[0]) {
  375. chosen |= PollData::Flag::Quiz;
  376. }
  377. }
  378. const auto replyTo = FullReplyTo();
  379. Window::PeerMenuCreatePoll(
  380. controller,
  381. item->history()->peer,
  382. replyTo,
  383. chosen,
  384. disabled);
  385. } break;
  386. case ButtonType::RequestPeer: {
  387. HideSingleUseKeyboard(controller, item);
  388. auto query = RequestPeerQuery();
  389. Assert(button->data.size() == sizeof(query));
  390. memcpy(&query, button->data.data(), sizeof(query));
  391. const auto peer = item->history()->peer;
  392. const auto itemId = item->id;
  393. const auto id = int32(button->buttonId);
  394. const auto chosen = [=](std::vector<not_null<PeerData*>> result) {
  395. peer->session().api().request(MTPmessages_SendBotRequestedPeer(
  396. peer->input,
  397. MTP_int(itemId),
  398. MTP_int(id),
  399. MTP_vector_from_range(
  400. result | ranges::views::transform([](
  401. not_null<PeerData*> peer) {
  402. return MTPInputPeer(peer->input);
  403. }))
  404. )).done([=](const MTPUpdates &result) {
  405. peer->session().api().applyUpdates(result);
  406. }).send();
  407. };
  408. if (const auto bot = item->getMessageBot()) {
  409. ShowChoosePeerBox(controller, bot, query, chosen);
  410. } else {
  411. LOG(("API Error: Bot not found for RequestPeer button."));
  412. }
  413. } break;
  414. case ButtonType::SwitchInlineSame:
  415. case ButtonType::SwitchInline: {
  416. if (const auto bot = item->getMessageBot()) {
  417. const auto fastSwitchDone = [&] {
  418. const auto samePeer = (button->type
  419. == ButtonType::SwitchInlineSame);
  420. if (samePeer) {
  421. SwitchInlineBotButtonReceived(
  422. controller,
  423. button->data,
  424. bot,
  425. item->id);
  426. return true;
  427. } else if (bot->isBot() && bot->botInfo->inlineReturnTo.key) {
  428. const auto switched = SwitchInlineBotButtonReceived(
  429. controller,
  430. button->data);
  431. if (switched) {
  432. return true;
  433. }
  434. }
  435. return false;
  436. }();
  437. if (!fastSwitchDone) {
  438. const auto query = QString::fromUtf8(button->data);
  439. const auto chosen = [=](not_null<Data::Thread*> thread) {
  440. return controller->switchInlineQuery(
  441. thread,
  442. bot,
  443. query);
  444. };
  445. Window::ShowChooseRecipientBox(
  446. controller,
  447. chosen,
  448. tr::lng_inline_switch_choose(),
  449. nullptr,
  450. button->peerTypes);
  451. }
  452. }
  453. } break;
  454. case ButtonType::Auth:
  455. UrlAuthBox::Activate(item, row, column);
  456. break;
  457. case ButtonType::UserProfile: {
  458. const auto session = &item->history()->session();
  459. const auto userId = UserId(button->data.toULongLong());
  460. if (const auto user = session->data().userLoaded(userId)) {
  461. controller->showPeerInfo(user);
  462. }
  463. } break;
  464. case ButtonType::WebView: {
  465. if (const auto bot = item->getMessageBot()) {
  466. bot->session().attachWebView().open({
  467. .bot = bot,
  468. .context = { .controller = controller },
  469. .button = { .text = button->text, .url = button->data },
  470. .source = InlineBots::WebViewSourceButton{ .simple = false },
  471. });
  472. }
  473. } break;
  474. case ButtonType::SimpleWebView: {
  475. if (const auto bot = item->getMessageBot()) {
  476. bot->session().attachWebView().open({
  477. .bot = bot,
  478. .context = { .controller = controller },
  479. .button = { .text = button->text, .url = button->data },
  480. .source = InlineBots::WebViewSourceButton{ .simple = true },
  481. });
  482. }
  483. } break;
  484. case ButtonType::CopyText: {
  485. const auto text = QString::fromUtf8(button->data);
  486. if (!text.isEmpty()) {
  487. QGuiApplication::clipboard()->setText(text);
  488. controller->showToast(tr::lng_text_copied(tr::now));
  489. }
  490. } break;
  491. }
  492. }
  493. } // namespace Api