sponsored_messages.cpp 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675
  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 "data/components/sponsored_messages.h"
  8. #include "api/api_text_entities.h"
  9. #include "apiwrap.h"
  10. #include "core/click_handler_types.h"
  11. #include "data/data_channel.h"
  12. #include "data/data_document.h"
  13. #include "data/data_file_origin.h"
  14. #include "data/data_media_preload.h"
  15. #include "data/data_photo.h"
  16. #include "data/data_session.h"
  17. #include "data/data_user.h"
  18. #include "history/history.h"
  19. #include "history/view/history_view_element.h"
  20. #include "lang/lang_keys.h"
  21. #include "main/main_session.h"
  22. #include "ui/chat/sponsored_message_bar.h"
  23. #include "ui/text/text_utilities.h" // Ui::Text::RichLangValue.
  24. namespace Data {
  25. namespace {
  26. constexpr auto kRequestTimeLimit = 5 * 60 * crl::time(1000);
  27. [[nodiscard]] bool TooEarlyForRequest(crl::time received) {
  28. return (received > 0) && (received + kRequestTimeLimit > crl::now());
  29. }
  30. } // namespace
  31. SponsoredMessages::SponsoredMessages(not_null<Main::Session*> session)
  32. : _session(session)
  33. , _clearTimer([=] { clearOldRequests(); }) {
  34. }
  35. SponsoredMessages::~SponsoredMessages() {
  36. Expects(_data.empty());
  37. Expects(_requests.empty());
  38. Expects(_viewRequests.empty());
  39. }
  40. void SponsoredMessages::clear() {
  41. _lifetime.destroy();
  42. for (const auto &request : base::take(_requests)) {
  43. _session->api().request(request.second.requestId).cancel();
  44. }
  45. for (const auto &request : base::take(_viewRequests)) {
  46. _session->api().request(request.second.requestId).cancel();
  47. }
  48. base::take(_data);
  49. }
  50. void SponsoredMessages::clearOldRequests() {
  51. const auto now = crl::now();
  52. while (true) {
  53. const auto i = ranges::find_if(_requests, [&](const auto &value) {
  54. const auto &request = value.second;
  55. return !request.requestId
  56. && (request.lastReceived + kRequestTimeLimit <= now);
  57. });
  58. if (i == end(_requests)) {
  59. break;
  60. }
  61. _requests.erase(i);
  62. }
  63. }
  64. SponsoredMessages::AppendResult SponsoredMessages::append(
  65. not_null<History*> history) {
  66. if (isTopBarFor(history)) {
  67. return SponsoredMessages::AppendResult::None;
  68. }
  69. const auto it = _data.find(history);
  70. if (it == end(_data)) {
  71. return SponsoredMessages::AppendResult::None;
  72. }
  73. auto &list = it->second;
  74. if (list.showedAll
  75. || !TooEarlyForRequest(list.received)
  76. || list.postsBetween) {
  77. return SponsoredMessages::AppendResult::None;
  78. }
  79. const auto entryIt = ranges::find_if(list.entries, [](const Entry &e) {
  80. return e.item == nullptr;
  81. });
  82. if (entryIt == end(list.entries)) {
  83. list.showedAll = true;
  84. return SponsoredMessages::AppendResult::None;
  85. } else if (entryIt->preload) {
  86. return SponsoredMessages::AppendResult::MediaLoading;
  87. }
  88. entryIt->item.reset(history->addSponsoredMessage(
  89. entryIt->itemFullId.msg,
  90. entryIt->sponsored.from,
  91. entryIt->sponsored.textWithEntities));
  92. return SponsoredMessages::AppendResult::Appended;
  93. }
  94. void SponsoredMessages::inject(
  95. not_null<History*> history,
  96. MsgId injectAfterMsgId,
  97. int betweenHeight,
  98. int fallbackWidth) {
  99. if (!canHaveFor(history)) {
  100. return;
  101. }
  102. const auto it = _data.find(history);
  103. if (it == end(_data)) {
  104. return;
  105. }
  106. auto &list = it->second;
  107. if (!list.postsBetween || (list.entries.size() == list.injectedCount)) {
  108. return;
  109. }
  110. while (true) {
  111. const auto entryIt = ranges::find_if(list.entries, [](const auto &e) {
  112. return e.item == nullptr;
  113. });
  114. if (entryIt == end(list.entries)) {
  115. list.showedAll = true;
  116. return;
  117. }
  118. const auto lastView = (entryIt != begin(list.entries))
  119. ? (entryIt - 1)->item->mainView()
  120. : (injectAfterMsgId == ShowAtUnreadMsgId)
  121. ? history->firstUnreadMessage()
  122. : [&] {
  123. const auto message = history->peer->owner().message(
  124. history->peer->id,
  125. injectAfterMsgId);
  126. return message ? message->mainView() : nullptr;
  127. }();
  128. if (!lastView || !lastView->block()) {
  129. return;
  130. }
  131. auto summaryBetween = 0;
  132. auto summaryHeight = 0;
  133. using BlockPtr = std::unique_ptr<HistoryBlock>;
  134. using ViewPtr = std::unique_ptr<HistoryView::Element>;
  135. auto blockIt = ranges::find(
  136. history->blocks,
  137. lastView->block(),
  138. &BlockPtr::get);
  139. if (blockIt == end(history->blocks)) {
  140. return;
  141. }
  142. const auto messages = [&]() -> const std::vector<ViewPtr>& {
  143. return (*blockIt)->messages;
  144. };
  145. auto lastViewIt = ranges::find(messages(), lastView, &ViewPtr::get);
  146. auto appendAtLeastToEnd = false;
  147. while ((summaryBetween < list.postsBetween)
  148. || (summaryHeight < betweenHeight)) {
  149. lastViewIt++;
  150. if (lastViewIt == end(messages())) {
  151. blockIt++;
  152. if (blockIt != end(history->blocks)) {
  153. lastViewIt = begin(messages());
  154. } else {
  155. if (!list.injectedCount) {
  156. appendAtLeastToEnd = true;
  157. break;
  158. }
  159. return;
  160. }
  161. }
  162. summaryBetween++;
  163. const auto viewHeight = (*lastViewIt)->height();
  164. summaryHeight += viewHeight
  165. ? viewHeight
  166. : (*lastViewIt)->resizeGetHeight(fallbackWidth);
  167. }
  168. // SponsoredMessages::Details can be requested within
  169. // the constructor of HistoryItem, so itemFullId is used as a key.
  170. entryIt->itemFullId = FullMsgId(
  171. history->peer->id,
  172. _session->data().nextLocalMessageId());
  173. if (appendAtLeastToEnd) {
  174. entryIt->item.reset(history->addSponsoredMessage(
  175. entryIt->itemFullId.msg,
  176. entryIt->sponsored.from,
  177. entryIt->sponsored.textWithEntities));
  178. } else {
  179. const auto makedMessage = history->makeMessage(
  180. entryIt->itemFullId.msg,
  181. entryIt->sponsored.from,
  182. entryIt->sponsored.textWithEntities,
  183. (*lastViewIt)->data());
  184. entryIt->item.reset(makedMessage.get());
  185. history->addNewInTheMiddle(
  186. makedMessage.get(),
  187. std::distance(begin(history->blocks), blockIt),
  188. std::distance(begin(messages()), lastViewIt) + 1);
  189. messages().back().get()->setPendingResize();
  190. }
  191. list.injectedCount++;
  192. }
  193. }
  194. bool SponsoredMessages::canHaveFor(not_null<History*> history) const {
  195. if (history->peer->isChannel()) {
  196. return true;
  197. } else if (const auto user = history->peer->asUser()) {
  198. return user->isBot();
  199. }
  200. return false;
  201. }
  202. bool SponsoredMessages::isTopBarFor(not_null<History*> history) const {
  203. if (peerIsUser(history->peer->id)) {
  204. if (const auto user = history->peer->asUser()) {
  205. return user->isBot();
  206. }
  207. }
  208. return false;
  209. }
  210. void SponsoredMessages::request(not_null<History*> history, Fn<void()> done) {
  211. if (!canHaveFor(history)) {
  212. return;
  213. }
  214. auto &request = _requests[history];
  215. if (request.requestId || TooEarlyForRequest(request.lastReceived)) {
  216. return;
  217. }
  218. {
  219. const auto it = _data.find(history);
  220. if (it != end(_data)) {
  221. auto &list = it->second;
  222. // Don't rebuild currently displayed messages.
  223. const auto proj = [](const Entry &e) {
  224. return e.item != nullptr;
  225. };
  226. if (ranges::any_of(list.entries, proj)) {
  227. return;
  228. }
  229. }
  230. }
  231. request.requestId = _session->api().request(
  232. MTPmessages_GetSponsoredMessages(history->peer->input)
  233. ).done([=](const MTPmessages_sponsoredMessages &result) {
  234. parse(history, result);
  235. if (done) {
  236. done();
  237. }
  238. }).fail([=] {
  239. _requests.remove(history);
  240. }).send();
  241. }
  242. void SponsoredMessages::parse(
  243. not_null<History*> history,
  244. const MTPmessages_sponsoredMessages &list) {
  245. auto &request = _requests[history];
  246. request.lastReceived = crl::now();
  247. request.requestId = 0;
  248. if (!_clearTimer.isActive()) {
  249. _clearTimer.callOnce(kRequestTimeLimit * 2);
  250. }
  251. list.match([&](const MTPDmessages_sponsoredMessages &data) {
  252. _session->data().processUsers(data.vusers());
  253. _session->data().processChats(data.vchats());
  254. const auto &messages = data.vmessages().v;
  255. auto &list = _data.emplace(history, List()).first->second;
  256. list.entries.clear();
  257. list.received = crl::now();
  258. for (const auto &message : messages) {
  259. append(history, list, message);
  260. }
  261. if (const auto postsBetween = data.vposts_between()) {
  262. list.postsBetween = postsBetween->v;
  263. list.state = State::InjectToMiddle;
  264. } else {
  265. list.state = history->peer->isChannel()
  266. ? State::AppendToEnd
  267. : State::AppendToTopBar;
  268. }
  269. }, [](const MTPDmessages_sponsoredMessagesEmpty &) {
  270. });
  271. }
  272. FullMsgId SponsoredMessages::fillTopBar(
  273. not_null<History*> history,
  274. not_null<Ui::RpWidget*> widget) {
  275. const auto it = _data.find(history);
  276. if (it != end(_data)) {
  277. auto &list = it->second;
  278. if (!list.entries.empty()) {
  279. const auto &entry = list.entries.front();
  280. const auto fullId = entry.itemFullId;
  281. Ui::FillSponsoredMessageBar(
  282. widget,
  283. _session,
  284. fullId,
  285. entry.sponsored.from,
  286. entry.sponsored.textWithEntities);
  287. return fullId;
  288. }
  289. }
  290. return {};
  291. }
  292. rpl::producer<> SponsoredMessages::itemRemoved(const FullMsgId &fullId) {
  293. if (IsServerMsgId(fullId.msg) || !fullId) {
  294. return rpl::never<>();
  295. }
  296. const auto history = _session->data().history(fullId.peer);
  297. const auto it = _data.find(history);
  298. if (it == end(_data)) {
  299. return rpl::never<>();
  300. }
  301. auto &list = it->second;
  302. const auto entryIt = ranges::find_if(list.entries, [&](const Entry &e) {
  303. return e.itemFullId == fullId;
  304. });
  305. if (entryIt == end(list.entries)) {
  306. return rpl::never<>();
  307. }
  308. if (!entryIt->optionalDestructionNotifier) {
  309. entryIt->optionalDestructionNotifier
  310. = std::make_unique<rpl::lifetime>();
  311. entryIt->optionalDestructionNotifier->add([this, fullId] {
  312. _itemRemoved.fire_copy(fullId);
  313. });
  314. }
  315. return _itemRemoved.events(
  316. ) | rpl::filter(rpl::mappers::_1 == fullId) | rpl::to_empty;
  317. }
  318. void SponsoredMessages::append(
  319. not_null<History*> history,
  320. List &list,
  321. const MTPSponsoredMessage &message) {
  322. const auto &data = message.data();
  323. const auto randomId = data.vrandom_id().v;
  324. auto mediaPhoto = (PhotoData*)nullptr;
  325. auto mediaDocument = (DocumentData*)nullptr;
  326. {
  327. if (data.vmedia()) {
  328. data.vmedia()->match([&](const MTPDmessageMediaPhoto &media) {
  329. if (const auto tlPhoto = media.vphoto()) {
  330. tlPhoto->match([&](const MTPDphoto &data) {
  331. mediaPhoto = history->owner().processPhoto(data);
  332. }, [](const MTPDphotoEmpty &) {
  333. });
  334. }
  335. }, [&](const MTPDmessageMediaDocument &media) {
  336. if (const auto tlDocument = media.vdocument()) {
  337. tlDocument->match([&](const MTPDdocument &data) {
  338. const auto d = history->owner().processDocument(
  339. data,
  340. media.valt_documents());
  341. if (d->isVideoFile()
  342. || d->isSilentVideo()
  343. || d->isAnimation()
  344. || d->isGifv()) {
  345. mediaDocument = d;
  346. }
  347. }, [](const MTPDdocumentEmpty &) {
  348. });
  349. }
  350. }, [](const auto &) {
  351. });
  352. }
  353. };
  354. const auto from = SponsoredFrom{
  355. .title = qs(data.vtitle()),
  356. .link = qs(data.vurl()),
  357. .buttonText = qs(data.vbutton_text()),
  358. .photoId = data.vphoto()
  359. ? history->session().data().processPhoto(*data.vphoto())->id
  360. : PhotoId(0),
  361. .mediaPhotoId = (mediaPhoto ? mediaPhoto->id : 0),
  362. .mediaDocumentId = (mediaDocument ? mediaDocument->id : 0),
  363. .backgroundEmojiId = data.vcolor().has_value()
  364. ? data.vcolor()->data().vbackground_emoji_id().value_or_empty()
  365. : uint64(0),
  366. .colorIndex = uint8(data.vcolor().has_value()
  367. ? data.vcolor()->data().vcolor().value_or_empty()
  368. : 0),
  369. .isLinkInternal = !UrlRequiresConfirmation(qs(data.vurl())),
  370. .isRecommended = data.is_recommended(),
  371. .canReport = data.is_can_report(),
  372. };
  373. auto sponsorInfo = data.vsponsor_info()
  374. ? tr::lng_sponsored_info_submenu(
  375. tr::now,
  376. lt_text,
  377. { .text = qs(*data.vsponsor_info()) },
  378. Ui::Text::RichLangValue)
  379. : TextWithEntities();
  380. auto additionalInfo = TextWithEntities::Simple(
  381. data.vadditional_info() ? qs(*data.vadditional_info()) : QString());
  382. auto sharedMessage = SponsoredMessage{
  383. .randomId = randomId,
  384. .from = from,
  385. .textWithEntities = {
  386. .text = qs(data.vmessage()),
  387. .entities = Api::EntitiesFromMTP(
  388. _session,
  389. data.ventities().value_or_empty()),
  390. },
  391. .history = history,
  392. .link = from.link,
  393. .sponsorInfo = std::move(sponsorInfo),
  394. .additionalInfo = std::move(additionalInfo),
  395. };
  396. list.entries.push_back({
  397. .sponsored = std::move(sharedMessage),
  398. });
  399. auto &entry = list.entries.back();
  400. const auto itemId = entry.itemFullId = FullMsgId(
  401. history->peer->id,
  402. _session->data().nextLocalMessageId());
  403. const auto fileOrigin = FileOrigin(); // No way to refresh in ads.
  404. static const auto kFlaggedPreload = ((MediaPreload*)quintptr(0x01));
  405. const auto preloaded = [=] {
  406. const auto i = _data.find(history);
  407. if (i == end(_data)) {
  408. return;
  409. }
  410. auto &entries = i->second.entries;
  411. const auto j = ranges::find(entries, itemId, &Entry::itemFullId);
  412. if (j == end(entries)) {
  413. return;
  414. }
  415. auto &entry = *j;
  416. if (entry.preload.get() == kFlaggedPreload) {
  417. entry.preload.release();
  418. } else {
  419. entry.preload = nullptr;
  420. }
  421. };
  422. auto preload = std::unique_ptr<MediaPreload>();
  423. entry.preload.reset(kFlaggedPreload);
  424. if (mediaPhoto) {
  425. preload = std::make_unique<PhotoPreload>(
  426. mediaPhoto,
  427. fileOrigin,
  428. preloaded);
  429. } else if (mediaDocument && VideoPreload::Can(mediaDocument)) {
  430. preload = std::make_unique<VideoPreload>(
  431. mediaDocument,
  432. fileOrigin,
  433. preloaded);
  434. }
  435. // Preload constructor may have called preloaded(), which zero-ed
  436. // entry.preload, that way we're ready and don't need to save it.
  437. // Otherwise we're preloading and need to save the task.
  438. if (entry.preload.get() == kFlaggedPreload) {
  439. entry.preload.release();
  440. if (preload) {
  441. entry.preload = std::move(preload);
  442. }
  443. }
  444. }
  445. void SponsoredMessages::clearItems(not_null<History*> history) {
  446. const auto it = _data.find(history);
  447. if (it == end(_data)) {
  448. return;
  449. }
  450. auto &list = it->second;
  451. for (auto &entry : list.entries) {
  452. entry.item.reset();
  453. }
  454. list.showedAll = false;
  455. list.injectedCount = 0;
  456. }
  457. const SponsoredMessages::Entry *SponsoredMessages::find(
  458. const FullMsgId &fullId) const {
  459. if (!peerIsChannel(fullId.peer) && !peerIsUser(fullId.peer)) {
  460. return nullptr;
  461. }
  462. const auto history = _session->data().history(fullId.peer);
  463. const auto it = _data.find(history);
  464. if (it == end(_data)) {
  465. return nullptr;
  466. }
  467. auto &list = it->second;
  468. const auto entryIt = ranges::find_if(list.entries, [&](const Entry &e) {
  469. return e.itemFullId == fullId;
  470. });
  471. if (entryIt == end(list.entries)) {
  472. return nullptr;
  473. }
  474. return &*entryIt;
  475. }
  476. void SponsoredMessages::view(const FullMsgId &fullId) {
  477. const auto entryPtr = find(fullId);
  478. if (!entryPtr) {
  479. return;
  480. }
  481. const auto randomId = entryPtr->sponsored.randomId;
  482. auto &request = _viewRequests[randomId];
  483. if (request.requestId || TooEarlyForRequest(request.lastReceived)) {
  484. return;
  485. }
  486. request.requestId = _session->api().request(
  487. MTPmessages_ViewSponsoredMessage(
  488. entryPtr->item
  489. ? entryPtr->item->history()->peer->input
  490. : _session->data().peer(fullId.peer)->input,
  491. MTP_bytes(randomId))
  492. ).done([=] {
  493. auto &request = _viewRequests[randomId];
  494. request.lastReceived = crl::now();
  495. request.requestId = 0;
  496. }).fail([=] {
  497. _viewRequests.remove(randomId);
  498. }).send();
  499. }
  500. SponsoredMessages::Details SponsoredMessages::lookupDetails(
  501. const FullMsgId &fullId) const {
  502. const auto entryPtr = find(fullId);
  503. if (!entryPtr) {
  504. return {};
  505. }
  506. const auto &data = entryPtr->sponsored;
  507. using InfoList = std::vector<TextWithEntities>;
  508. auto info = (!data.sponsorInfo.text.isEmpty()
  509. && !data.additionalInfo.text.isEmpty())
  510. ? InfoList{ data.sponsorInfo, data.additionalInfo }
  511. : !data.sponsorInfo.text.isEmpty()
  512. ? InfoList{ data.sponsorInfo }
  513. : !data.additionalInfo.text.isEmpty()
  514. ? InfoList{ data.additionalInfo }
  515. : InfoList{};
  516. return {
  517. .info = std::move(info),
  518. .link = data.link,
  519. .buttonText = data.from.buttonText,
  520. .photoId = data.from.photoId,
  521. .mediaPhotoId = data.from.mediaPhotoId,
  522. .mediaDocumentId = data.from.mediaDocumentId,
  523. .backgroundEmojiId = data.from.backgroundEmojiId,
  524. .colorIndex = data.from.colorIndex,
  525. .isLinkInternal = data.from.isLinkInternal,
  526. .canReport = data.from.canReport,
  527. };
  528. }
  529. void SponsoredMessages::clicked(
  530. const FullMsgId &fullId,
  531. bool isMedia,
  532. bool isFullscreen) {
  533. const auto entryPtr = find(fullId);
  534. if (!entryPtr) {
  535. return;
  536. }
  537. const auto randomId = entryPtr->sponsored.randomId;
  538. using Flag = MTPmessages_ClickSponsoredMessage::Flag;
  539. _session->api().request(MTPmessages_ClickSponsoredMessage(
  540. MTP_flags(Flag(0)
  541. | (isMedia ? Flag::f_media : Flag(0))
  542. | (isFullscreen ? Flag::f_fullscreen : Flag(0))),
  543. entryPtr->item
  544. ? entryPtr->item->history()->peer->input
  545. : _session->data().peer(fullId.peer)->input,
  546. MTP_bytes(randomId)
  547. )).send();
  548. }
  549. auto SponsoredMessages::createReportCallback(const FullMsgId &fullId)
  550. -> Fn<void(SponsoredReportResult::Id, Fn<void(SponsoredReportResult)>)> {
  551. using TLChoose = MTPDchannels_sponsoredMessageReportResultChooseOption;
  552. using TLAdsHidden = MTPDchannels_sponsoredMessageReportResultAdsHidden;
  553. using TLReported = MTPDchannels_sponsoredMessageReportResultReported;
  554. using Result = SponsoredReportResult;
  555. struct State final {
  556. #ifdef _DEBUG
  557. ~State() {
  558. qDebug() << "SponsoredMessages Report ~State().";
  559. }
  560. #endif
  561. mtpRequestId requestId = 0;
  562. };
  563. const auto state = std::make_shared<State>();
  564. return [=](Result::Id optionId, Fn<void(Result)> done) {
  565. const auto entry = find(fullId);
  566. if (!entry) {
  567. return;
  568. }
  569. const auto history = _session->data().history(fullId.peer);
  570. const auto erase = [=] {
  571. const auto it = _data.find(history);
  572. if (it != end(_data)) {
  573. auto &list = it->second.entries;
  574. const auto proj = [&](const Entry &e) {
  575. return e.itemFullId == fullId;
  576. };
  577. list.erase(ranges::remove_if(list, proj), end(list));
  578. }
  579. };
  580. if (optionId == Result::Id("-1")) {
  581. erase();
  582. return;
  583. }
  584. state->requestId = _session->api().request(
  585. MTPmessages_ReportSponsoredMessage(
  586. history->peer->input,
  587. MTP_bytes(entry->sponsored.randomId),
  588. MTP_bytes(optionId))
  589. ).done([=](
  590. const MTPchannels_SponsoredMessageReportResult &result,
  591. mtpRequestId requestId) {
  592. if (state->requestId != requestId) {
  593. return;
  594. }
  595. state->requestId = 0;
  596. done(result.match([&](const TLChoose &data) {
  597. const auto t = qs(data.vtitle());
  598. auto list = Result::Options();
  599. list.reserve(data.voptions().v.size());
  600. for (const auto &tl : data.voptions().v) {
  601. list.emplace_back(Result::Option{
  602. .id = tl.data().voption().v,
  603. .text = qs(tl.data().vtext()),
  604. });
  605. }
  606. return Result{ .options = std::move(list), .title = t };
  607. }, [](const TLAdsHidden &data) -> Result {
  608. return { .result = Result::FinalStep::Hidden };
  609. }, [&](const TLReported &data) -> Result {
  610. erase();
  611. if (optionId == Result::Id("1")) { // I don't like it.
  612. return { .result = Result::FinalStep::Silence };
  613. }
  614. return { .result = Result::FinalStep::Reported };
  615. }));
  616. }).fail([=](const MTP::Error &error) {
  617. state->requestId = 0;
  618. if (error.type() == u"PREMIUM_ACCOUNT_REQUIRED"_q) {
  619. done({ .result = Result::FinalStep::Premium });
  620. } else {
  621. done({ .error = error.type() });
  622. }
  623. }).send();
  624. };
  625. }
  626. SponsoredMessages::State SponsoredMessages::state(
  627. not_null<History*> history) const {
  628. const auto it = _data.find(history);
  629. return (it == end(_data)) ? State::None : it->second.state;
  630. }
  631. } // namespace Data