From e5ca9e4c3935e6309bdb878d4d19b7556d022a92 Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 8 Jul 2025 15:00:11 +0400 Subject: [PATCH] Ability to request video ads. --- .../data/components/sponsored_messages.cpp | 159 ++++++++++++++---- .../data/components/sponsored_messages.h | 25 ++- 2 files changed, 152 insertions(+), 32 deletions(-) diff --git a/Telegram/SourceFiles/data/components/sponsored_messages.cpp b/Telegram/SourceFiles/data/components/sponsored_messages.cpp index ffd8c2f20d..8fca3c2de2 100644 --- a/Telegram/SourceFiles/data/components/sponsored_messages.cpp +++ b/Telegram/SourceFiles/data/components/sponsored_messages.cpp @@ -30,6 +30,8 @@ namespace { constexpr auto kRequestTimeLimit = 5 * 60 * crl::time(1000); +const auto kFlaggedPreload = ((MediaPreload*)quintptr(0x01)); + [[nodiscard]] bool TooEarlyForRequest(crl::time received) { return (received > 0) && (received + kRequestTimeLimit > crl::now()); } @@ -73,17 +75,21 @@ void SponsoredMessages::clear() { void SponsoredMessages::clearOldRequests() { const auto now = crl::now(); - while (true) { - const auto i = ranges::find_if(_requests, [&](const auto &value) { - const auto &request = value.second; - return !request.requestId - && (request.lastReceived + kRequestTimeLimit <= now); - }); - if (i == end(_requests)) { - break; + const auto clear = [&](auto &requests) { + while (true) { + const auto i = ranges::find_if(requests, [&](const auto &value) { + const auto &request = value.second; + return !request.requestId + && (request.lastReceived + kRequestTimeLimit <= now); + }); + if (i == end(requests)) { + break; + } + requests.erase(i); } - _requests.erase(i); - } + }; + clear(_requests); + clear(_requestsForVideo); } SponsoredMessages::AppendResult SponsoredMessages::append( @@ -232,6 +238,11 @@ bool SponsoredMessages::canHaveFor(not_null history) const { return false; } +bool SponsoredMessages::canHaveFor(not_null item) const { + return item->history()->peer->isBroadcast() + && item->isRegular(); +} + bool SponsoredMessages::isTopBarFor(not_null history) const { if (peerIsUser(history->peer->id)) { if (const auto user = history->peer->asUser()) { @@ -277,6 +288,49 @@ void SponsoredMessages::request(not_null history, Fn done) { }).send(); } +void SponsoredMessages::request( + not_null item, + Fn done) { + Expects(done != nullptr); + + if (!canHaveFor(item)) { + done({}); + return; + } + const auto id = item->fullId(); + auto &request = _requestsForVideo[id]; + if (request.requestId) { + done(prepareForVideo(id)); + return; + } + { + const auto it = _dataForVideo.find(id); + if (it != end(_dataForVideo)) { + auto &list = it->second; + // Don't rebuild currently displayed messages. + const auto proj = [](const Entry &e) { + return e.item != nullptr; + }; + if (ranges::any_of(list.entries, proj)) { + return; + } + } + } + using Flag = MTPmessages_GetSponsoredMessages::Flag; + request.requestId = _session->api().request( + MTPmessages_GetSponsoredMessages( + MTP_flags(Flag::f_msg_id), + item->history()->peer->input, + MTP_int(item->id.bare)) + ).done([=](const MTPmessages_sponsoredMessages &result) { + parse(id, result); + done(prepareForVideo(id)); + }).fail([=] { + _requestsForVideo.remove(id); + done({}); + }).send(); +} + void SponsoredMessages::parse( not_null history, const MTPmessages_sponsoredMessages &list) { @@ -292,12 +346,9 @@ void SponsoredMessages::parse( _session->data().processChats(data.vchats()); const auto &messages = data.vmessages().v; - auto &list = _data.emplace(history, List()).first->second; + auto &list = _data.emplace(history).first->second; list.entries.clear(); list.received = crl::now(); - for (const auto &message : messages) { - append(history, list, message); - } if (const auto postsBetween = data.vposts_between()) { list.postsBetween = postsBetween->v; list.state = State::InjectToMiddle; @@ -306,10 +357,59 @@ void SponsoredMessages::parse( ? State::AppendToEnd : State::AppendToTopBar; } + for (const auto &message : messages) { + append([=] { + return &_data[history].entries; + }, history, message); + } }, [](const MTPDmessages_sponsoredMessagesEmpty &) { }); } +void SponsoredMessages::parse( + FullMsgId itemId, + const MTPmessages_sponsoredMessages &list) { + auto &request = _requestsForVideo[itemId]; + request.lastReceived = crl::now(); + request.requestId = 0; + if (!_clearTimer.isActive()) { + _clearTimer.callOnce(kRequestTimeLimit * 2); + } + + list.match([&](const MTPDmessages_sponsoredMessages &data) { + _session->data().processUsers(data.vusers()); + _session->data().processChats(data.vchats()); + + const auto history = _session->data().history(itemId.peer); + const auto &messages = data.vmessages().v; + auto &list = _dataForVideo.emplace(itemId).first->second; + list.entries.clear(); + list.received = crl::now(); + list.startDelay = data.vstart_delay().value_or_empty(); + list.betweenDelay = data.vbetween_delay().value_or_empty(); + for (const auto &message : messages) { + append([=] { + return &_dataForVideo[itemId].entries; + }, history, message); + } + }, [](const MTPDmessages_sponsoredMessagesEmpty &) { + }); +} + +SponsoredForVideo SponsoredMessages::prepareForVideo(FullMsgId itemId) { + const auto i = _dataForVideo.find(itemId); + if (i == end(_dataForVideo) || i->second.entries.empty()) { + return {}; + } + return SponsoredForVideo{ + .list = i->second.entries | ranges::views::transform( + &Entry::sponsored + ) | ranges::to_vector, + .startDelay = i->second.startDelay, + .betweenDelay = i->second.betweenDelay, + }; +} + FullMsgId SponsoredMessages::fillTopBar( not_null history, not_null widget) { @@ -359,8 +459,8 @@ rpl::producer<> SponsoredMessages::itemRemoved(const FullMsgId &fullId) { } void SponsoredMessages::append( + Fn*>()> entries, not_null history, - List &list, const MTPSponsoredMessage &message) { const auto &data = message.data(); const auto randomId = data.vrandom_id().v; @@ -371,14 +471,14 @@ void SponsoredMessages::append( data.vmedia()->match([&](const MTPDmessageMediaPhoto &media) { if (const auto tlPhoto = media.vphoto()) { tlPhoto->match([&](const MTPDphoto &data) { - mediaPhoto = history->owner().processPhoto(data); + mediaPhoto = _session->data().processPhoto(data); }, [](const MTPDphotoEmpty &) { }); } }, [&](const MTPDmessageMediaDocument &media) { if (const auto tlDocument = media.vdocument()) { tlDocument->match([&](const MTPDdocument &data) { - const auto d = history->owner().processDocument( + const auto d = _session->data().processDocument( data, media.valt_documents()); if (d->isVideoFile() @@ -399,7 +499,7 @@ void SponsoredMessages::append( .link = qs(data.vurl()), .buttonText = qs(data.vbutton_text()), .photoId = data.vphoto() - ? history->session().data().processPhoto(*data.vphoto())->id + ? _session->data().processPhoto(*data.vphoto())->id : PhotoId(0), .mediaPhotoId = (mediaPhoto ? mediaPhoto->id : 0), .mediaDocumentId = (mediaDocument ? mediaDocument->id : 0), @@ -436,24 +536,21 @@ void SponsoredMessages::append( .sponsorInfo = std::move(sponsorInfo), .additionalInfo = std::move(additionalInfo), }; - list.entries.push_back({ - .sponsored = std::move(sharedMessage), - }); - auto &entry = list.entries.back(); - const auto itemId = entry.itemFullId = FullMsgId( + const auto itemId = FullMsgId( history->peer->id, _session->data().nextLocalMessageId()); + const auto list = entries(); + list->push_back({ + .itemFullId = itemId, + .sponsored = std::move(sharedMessage), + }); + auto &entry = list->back(); const auto fileOrigin = FileOrigin(); // No way to refresh in ads. - static const auto kFlaggedPreload = ((MediaPreload*)quintptr(0x01)); const auto preloaded = [=] { - const auto i = _data.find(history); - if (i == end(_data)) { - return; - } - auto &entries = i->second.entries; - const auto j = ranges::find(entries, itemId, &Entry::itemFullId); - if (j == end(entries)) { + const auto list = entries(); + const auto j = ranges::find(*list, itemId, &Entry::itemFullId); + if (j == end(*list)) { return; } auto &entry = *j; diff --git a/Telegram/SourceFiles/data/components/sponsored_messages.h b/Telegram/SourceFiles/data/components/sponsored_messages.h index 6c875c081c..9aaa2a5f95 100644 --- a/Telegram/SourceFiles/data/components/sponsored_messages.h +++ b/Telegram/SourceFiles/data/components/sponsored_messages.h @@ -92,6 +92,12 @@ struct SponsoredReportAction { Fn)> callback; }; +struct SponsoredForVideo { + std::vector list; + crl::time startDelay = 0; + crl::time betweenDelay = 0; +}; + class SponsoredMessages final { public: enum class AppendResult { @@ -111,8 +117,12 @@ public: ~SponsoredMessages(); [[nodiscard]] bool canHaveFor(not_null history) const; + [[nodiscard]] bool canHaveFor(not_null item) const; [[nodiscard]] bool isTopBarFor(not_null history) const; void request(not_null history, Fn done); + void request( + not_null item, + Fn done); void clearItems(not_null history); [[nodiscard]] Details lookupDetails(const FullMsgId &fullId) const; [[nodiscard]] Details lookupDetails( @@ -166,6 +176,12 @@ private: int postsBetween = 0; State state = State::None; }; + struct ListForVideo { + std::vector entries; + crl::time received = 0; + crl::time startDelay = 0; + crl::time betweenDelay = 0; + }; struct Request { mtpRequestId requestId = 0; crl::time lastReceived = 0; @@ -174,10 +190,14 @@ private: void parse( not_null history, const MTPmessages_sponsoredMessages &list); + void parse( + FullMsgId itemId, + const MTPmessages_sponsoredMessages &list); void append( + Fn*>()> entries, not_null history, - List &list, const MTPSponsoredMessage &message); + [[nodiscard]] SponsoredForVideo prepareForVideo(FullMsgId itemId); void clearOldRequests(); const Entry *find(const FullMsgId &fullId) const; @@ -189,6 +209,9 @@ private: base::flat_map, Request> _requests; base::flat_map _viewRequests; + base::flat_map _dataForVideo; + base::flat_map _requestsForVideo; + rpl::event_stream _itemRemoved; rpl::lifetime _lifetime;