| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945 |
- /*
- This file is part of Telegram Desktop,
- the official desktop application for the Telegram messaging service.
- For license and copyright information please follow this link:
- https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
- */
- #include "data/data_story.h"
- #include "base/unixtime.h"
- #include "api/api_text_entities.h"
- #include "data/data_document.h"
- #include "data/data_changes.h"
- #include "data/data_channel.h"
- #include "data/data_file_origin.h"
- #include "data/data_media_preload.h"
- #include "data/data_photo.h"
- #include "data/data_photo_media.h"
- #include "data/data_user.h"
- #include "data/data_session.h"
- #include "data/data_stories.h"
- #include "data/data_thread.h"
- #include "history/history_item.h"
- #include "lang/lang_keys.h"
- #include "main/main_session.h"
- #include "media/streaming/media_streaming_reader.h"
- #include "storage/download_manager_mtproto.h"
- #include "storage/file_download.h" // kMaxFileInMemory
- #include "ui/text/text_utilities.h"
- #include "ui/color_int_conversion.h"
- namespace Data {
- namespace {
- using UpdateFlag = StoryUpdate::Flag;
- [[nodiscard]] StoryArea ParseArea(const MTPMediaAreaCoordinates &area) {
- const auto &data = area.data();
- const auto center = QPointF(data.vx().v, data.vy().v);
- const auto size = QSizeF(data.vw().v, data.vh().v);
- const auto corner = center - QPointF(size.width(), size.height()) / 2.;
- return {
- .geometry = { corner / 100., size / 100. },
- .rotation = data.vrotation().v,
- .radius = data.vradius().value_or_empty(),
- };
- }
- [[nodiscard]] TextWithEntities StripLinks(TextWithEntities text) {
- const auto link = [&](const EntityInText &entity) {
- return (entity.type() == EntityType::CustomUrl)
- || (entity.type() == EntityType::Url)
- || (entity.type() == EntityType::Mention)
- || (entity.type() == EntityType::Hashtag);
- };
- text.entities.erase(
- ranges::remove_if(text.entities, link),
- text.entities.end());
- return text;
- }
- [[nodiscard]] auto ParseLocation(const MTPMediaArea &area)
- -> std::optional<StoryLocation> {
- auto result = std::optional<StoryLocation>();
- area.match([&](const MTPDmediaAreaVenue &data) {
- data.vgeo().match([&](const MTPDgeoPoint &geo) {
- result.emplace(StoryLocation{
- .area = ParseArea(data.vcoordinates()),
- .point = Data::LocationPoint(geo),
- .title = qs(data.vtitle()),
- .address = qs(data.vaddress()),
- .provider = qs(data.vprovider()),
- .venueId = qs(data.vvenue_id()),
- .venueType = qs(data.vvenue_type()),
- });
- }, [](const MTPDgeoPointEmpty &) {
- });
- }, [&](const MTPDmediaAreaGeoPoint &data) {
- data.vgeo().match([&](const MTPDgeoPoint &geo) {
- result.emplace(StoryLocation{
- .area = ParseArea(data.vcoordinates()),
- .point = Data::LocationPoint(geo),
- });
- }, [](const MTPDgeoPointEmpty &) {
- });
- }, [&](const MTPDmediaAreaSuggestedReaction &data) {
- }, [&](const MTPDmediaAreaChannelPost &data) {
- }, [&](const MTPDmediaAreaUrl &data) {
- }, [&](const MTPDmediaAreaWeather &data) {
- }, [&](const MTPDmediaAreaStarGift &data) {
- }, [&](const MTPDinputMediaAreaChannelPost &data) {
- LOG(("API Error: Unexpected inputMediaAreaChannelPost from API."));
- }, [&](const MTPDinputMediaAreaVenue &data) {
- LOG(("API Error: Unexpected inputMediaAreaVenue from API."));
- });
- return result;
- }
- [[nodiscard]] auto ParseSuggestedReaction(const MTPMediaArea &area)
- -> std::optional<SuggestedReaction> {
- auto result = std::optional<SuggestedReaction>();
- area.match([&](const MTPDmediaAreaVenue &data) {
- }, [&](const MTPDmediaAreaGeoPoint &data) {
- }, [&](const MTPDmediaAreaSuggestedReaction &data) {
- result.emplace(SuggestedReaction{
- .area = ParseArea(data.vcoordinates()),
- .reaction = Data::ReactionFromMTP(data.vreaction()),
- .flipped = data.is_flipped(),
- .dark = data.is_dark(),
- });
- }, [&](const MTPDmediaAreaChannelPost &data) {
- }, [&](const MTPDmediaAreaUrl &data) {
- }, [&](const MTPDmediaAreaWeather &data) {
- }, [&](const MTPDmediaAreaStarGift &data) {
- }, [&](const MTPDinputMediaAreaChannelPost &data) {
- LOG(("API Error: Unexpected inputMediaAreaChannelPost from API."));
- }, [&](const MTPDinputMediaAreaVenue &data) {
- LOG(("API Error: Unexpected inputMediaAreaVenue from API."));
- });
- return result;
- }
- [[nodiscard]] auto ParseChannelPost(const MTPMediaArea &area)
- -> std::optional<ChannelPost> {
- auto result = std::optional<ChannelPost>();
- area.match([&](const MTPDmediaAreaVenue &data) {
- }, [&](const MTPDmediaAreaGeoPoint &data) {
- }, [&](const MTPDmediaAreaSuggestedReaction &data) {
- }, [&](const MTPDmediaAreaChannelPost &data) {
- result.emplace(ChannelPost{
- .area = ParseArea(data.vcoordinates()),
- .itemId = FullMsgId(
- peerFromChannel(data.vchannel_id()),
- data.vmsg_id().v),
- });
- }, [&](const MTPDmediaAreaUrl &data) {
- }, [&](const MTPDmediaAreaWeather &data) {
- }, [&](const MTPDmediaAreaStarGift &data) {
- }, [&](const MTPDinputMediaAreaChannelPost &data) {
- LOG(("API Error: Unexpected inputMediaAreaChannelPost from API."));
- }, [&](const MTPDinputMediaAreaVenue &data) {
- LOG(("API Error: Unexpected inputMediaAreaVenue from API."));
- });
- return result;
- }
- [[nodiscard]] auto ParseUrlArea(const MTPMediaArea &area)
- -> std::optional<UrlArea> {
- auto result = std::optional<UrlArea>();
- area.match([&](const MTPDmediaAreaVenue &data) {
- }, [&](const MTPDmediaAreaGeoPoint &data) {
- }, [&](const MTPDmediaAreaSuggestedReaction &data) {
- }, [&](const MTPDmediaAreaChannelPost &data) {
- }, [&](const MTPDmediaAreaUrl &data) {
- result.emplace(UrlArea{
- .area = ParseArea(data.vcoordinates()),
- .url = qs(data.vurl()),
- });
- }, [&](const MTPDmediaAreaWeather &data) {
- }, [&](const MTPDmediaAreaStarGift &data) {
- result.emplace(UrlArea{
- .area = ParseArea(data.vcoordinates()),
- .url = u"tg://nft?slug="_q + qs(data.vslug()),
- });
- }, [&](const MTPDinputMediaAreaChannelPost &data) {
- LOG(("API Error: Unexpected inputMediaAreaChannelPost from API."));
- }, [&](const MTPDinputMediaAreaVenue &data) {
- LOG(("API Error: Unexpected inputMediaAreaVenue from API."));
- });
- return result;
- }
- [[nodiscard]] auto ParseWeatherArea(const MTPMediaArea &area)
- -> std::optional<WeatherArea> {
- auto result = std::optional<WeatherArea>();
- area.match([&](const MTPDmediaAreaVenue &data) {
- }, [&](const MTPDmediaAreaGeoPoint &data) {
- }, [&](const MTPDmediaAreaSuggestedReaction &data) {
- }, [&](const MTPDmediaAreaChannelPost &data) {
- }, [&](const MTPDmediaAreaUrl &data) {
- }, [&](const MTPDmediaAreaWeather &data) {
- result.emplace(WeatherArea{
- .area = ParseArea(data.vcoordinates()),
- .emoji = qs(data.vemoji()),
- .color = Ui::Color32FromSerialized(data.vcolor().v),
- .millicelsius = int(1000. * std::clamp(
- data.vtemperature_c().v,
- -274.,
- 1'000'000.)),
- });
- }, [&](const MTPDmediaAreaStarGift &data) {
- }, [&](const MTPDinputMediaAreaChannelPost &data) {
- LOG(("API Error: Unexpected inputMediaAreaChannelPost from API."));
- }, [&](const MTPDinputMediaAreaVenue &data) {
- LOG(("API Error: Unexpected inputMediaAreaVenue from API."));
- });
- return result;
- }
- [[nodiscard]] PeerData *RepostSourcePeer(
- not_null<Session*> owner,
- const MTPDstoryItem &data) {
- if (const auto forwarded = data.vfwd_from()) {
- if (const auto from = forwarded->data().vfrom()) {
- return owner->peer(peerFromMTP(*from));
- }
- }
- return nullptr;
- }
- [[nodiscard]] QString RepostSourceName(const MTPDstoryItem &data) {
- if (const auto forwarded = data.vfwd_from()) {
- return qs(forwarded->data().vfrom_name().value_or_empty());
- }
- return {};
- }
- [[nodiscard]] StoryId RepostSourceId(const MTPDstoryItem &data) {
- if (const auto forwarded = data.vfwd_from()) {
- return forwarded->data().vstory_id().value_or_empty();
- }
- return {};
- }
- [[nodiscard]] bool RepostModified(const MTPDstoryItem &data) {
- if (const auto forwarded = data.vfwd_from()) {
- return forwarded->data().is_modified();
- }
- return false;
- }
- [[nodiscard]] PeerData *FromPeer(
- not_null<Session*> owner,
- const MTPDstoryItem &data) {
- if (const auto from = data.vfrom_id()) {
- return owner->peer(peerFromMTP(*from));
- }
- return nullptr;
- }
- } // namespace
- Story::Story(
- StoryId id,
- not_null<PeerData*> peer,
- StoryMedia media,
- const MTPDstoryItem &data,
- TimeId now)
- : _id(id)
- , _peer(peer)
- , _repostSourcePeer(RepostSourcePeer(&peer->owner(), data))
- , _repostSourceName(RepostSourceName(data))
- , _repostSourceId(RepostSourceId(data))
- , _fromPeer(FromPeer(&peer->owner(), data))
- , _date(data.vdate().v)
- , _expires(data.vexpire_date().v)
- , _repostModified(RepostModified(data)) {
- applyFields(std::move(media), data, now, true);
- }
- Session &Story::owner() const {
- return _peer->owner();
- }
- Main::Session &Story::session() const {
- return _peer->session();
- }
- not_null<PeerData*> Story::peer() const {
- return _peer;
- }
- StoryId Story::id() const {
- return _id;
- }
- bool Story::mine() const {
- return _peer->isSelf();
- }
- StoryIdDates Story::idDates() const {
- return { _id, _date, _expires };
- }
- FullStoryId Story::fullId() const {
- return { _peer->id, _id };
- }
- TimeId Story::date() const {
- return _date;
- }
- TimeId Story::expires() const {
- return _expires;
- }
- bool Story::expired(TimeId now) const {
- return _expires <= (now ? now : base::unixtime::now());
- }
- bool Story::unsupported() const {
- return v::is_null(_media.data);
- }
- const StoryMedia &Story::media() const {
- return _media;
- }
- PhotoData *Story::photo() const {
- const auto result = std::get_if<not_null<PhotoData*>>(&_media.data);
- return result ? result->get() : nullptr;
- }
- DocumentData *Story::document() const {
- const auto result = std::get_if<not_null<DocumentData*>>(&_media.data);
- return result ? result->get() : nullptr;
- }
- bool Story::hasReplyPreview() const {
- return v::match(_media.data, [](not_null<PhotoData*> photo) {
- return !photo->isNull();
- }, [](not_null<DocumentData*> document) {
- return document->hasThumbnail();
- }, [](v::null_t) {
- return false;
- });
- }
- Image *Story::replyPreview() const {
- return v::match(_media.data, [&](not_null<PhotoData*> photo) {
- return photo->getReplyPreview(fullId(), _peer, false);
- }, [&](not_null<DocumentData*> document) {
- return document->getReplyPreview(fullId(), _peer, false);
- }, [](v::null_t) {
- return (Image*)nullptr;
- });
- }
- TextWithEntities Story::inReplyText() const {
- const auto type = tr::lng_in_dlg_story(tr::now);
- return _caption.text.isEmpty()
- ? Ui::Text::Colorized(type)
- : tr::lng_dialogs_text_media(
- tr::now,
- lt_media_part,
- tr::lng_dialogs_text_media_wrapped(
- tr::now,
- lt_media,
- Ui::Text::Colorized(type),
- Ui::Text::WithEntities),
- lt_caption,
- _caption,
- Ui::Text::WithEntities);
- }
- void Story::setPinnedToTop(bool pinned) {
- if (_pinnedToTop != pinned) {
- _pinnedToTop = pinned;
- if (const auto item = _peer->owner().stories().lookupItem(this)) {
- item->setIsPinned(pinned);
- }
- }
- }
- bool Story::pinnedToTop() const {
- return _pinnedToTop;
- }
- void Story::setInProfile(bool value) {
- _inProfile = value;
- }
- bool Story::inProfile() const {
- return _inProfile;
- }
- StoryPrivacy Story::privacy() const {
- return _privacyPublic
- ? StoryPrivacy::Public
- : _privacyCloseFriends
- ? StoryPrivacy::CloseFriends
- : _privacyContacts
- ? StoryPrivacy::Contacts
- : _privacySelectedContacts
- ? StoryPrivacy::SelectedContacts
- : StoryPrivacy::Other;
- }
- bool Story::forbidsForward() const {
- return _noForwards;
- }
- bool Story::edited() const {
- return _edited;
- }
- bool Story::out() const {
- return _out;
- }
- bool Story::canDownloadIfPremium() const {
- return !forbidsForward() || _peer->isSelf();
- }
- bool Story::canDownloadChecked() const {
- return _peer->isSelf()
- || (canDownloadIfPremium() && _peer->session().premium());
- }
- bool Story::canShare() const {
- return _privacyPublic
- && !forbidsForward()
- && (inProfile() || !expired());
- }
- bool Story::canDelete() const {
- if (const auto channel = _peer->asChannel()) {
- return channel->canDeleteStories()
- || (out() && channel->canPostStories());
- }
- return _peer->isSelf();
- }
- bool Story::canReport() const {
- return !_peer->isSelf();
- }
- bool Story::hasDirectLink() const {
- if (!_privacyPublic || (!_inProfile && expired())) {
- return false;
- }
- return !_peer->username().isEmpty();
- }
- Data::SendError Story::errorTextForForward(
- not_null<Thread*> to) const {
- const auto peer = to->peer();
- const auto holdsPhoto = v::is<not_null<PhotoData*>>(_media.data);
- const auto first = holdsPhoto
- ? ChatRestriction::SendPhotos
- : ChatRestriction::SendVideos;
- const auto second = holdsPhoto
- ? ChatRestriction::SendVideos
- : ChatRestriction::SendPhotos;
- if (const auto one = Data::RestrictionError(peer, first)) {
- return one;
- } else if (const auto two = Data::RestrictionError(peer, second)) {
- return two;
- } else if (!Data::CanSend(to, first, false)
- || !Data::CanSend(to, second, false)) {
- return tr::lng_forward_cant(tr::now);
- }
- return {};
- }
- void Story::setCaption(TextWithEntities &&caption) {
- _caption = std::move(caption);
- }
- const TextWithEntities &Story::caption() const {
- static const auto empty = TextWithEntities();
- return unsupported() ? empty : _caption;
- }
- Data::ReactionId Story::sentReactionId() const {
- return _sentReactionId;
- }
- void Story::setReactionId(Data::ReactionId id) {
- if (_sentReactionId != id) {
- const auto wasEmpty = _sentReactionId.empty();
- changeSuggestedReactionCount(_sentReactionId, -1);
- _sentReactionId = id;
- changeSuggestedReactionCount(id, 1);
- if (_views.known && _sentReactionId.empty() != wasEmpty) {
- const auto delta = wasEmpty ? 1 : -1;
- if (_views.reactions + delta >= 0) {
- _views.reactions += delta;
- }
- }
- session().changes().storyUpdated(this, UpdateFlag::Reaction);
- }
- }
- void Story::changeSuggestedReactionCount(Data::ReactionId id, int delta) {
- if (id.empty() || !_peer->isChannel()) {
- return;
- }
- for (auto &suggested : _suggestedReactions) {
- if (suggested.reaction == id && suggested.count + delta >= 0) {
- suggested.count += delta;
- }
- }
- }
- const std::vector<not_null<PeerData*>> &Story::recentViewers() const {
- return _recentViewers;
- }
- const StoryViews &Story::viewsList() const {
- return _views;
- }
- const StoryViews &Story::channelReactionsList() const {
- return _channelReactions;
- }
- int Story::interactions() const {
- return _views.total;
- }
- int Story::views() const {
- return _views.views;
- }
- int Story::forwards() const {
- return _views.forwards;
- }
- int Story::reactions() const {
- return _views.reactions;
- }
- void Story::applyViewsSlice(
- const QString &offset,
- const StoryViews &slice) {
- const auto changed = (_views.reactions != slice.reactions)
- || (_views.views != slice.views)
- || (_views.forwards != slice.forwards)
- || (_views.total != slice.total);
- _views.reactions = slice.reactions;
- _views.forwards = slice.forwards;
- _views.views = slice.views;
- _views.total = slice.total;
- _views.known = true;
- if (offset.isEmpty()) {
- _views = slice;
- if (!_channelReactions.total) {
- _channelReactions.total = _views.reactions + _views.forwards;
- }
- } else if (_views.nextOffset == offset) {
- _views.list.insert(
- end(_views.list),
- begin(slice.list),
- end(slice.list));
- _views.nextOffset = slice.nextOffset;
- if (_views.nextOffset.isEmpty()) {
- _views.total = int(_views.list.size());
- _views.reactions = _views.total
- - ranges::count(
- _views.list,
- Data::ReactionId(),
- &StoryView::reaction);
- _views.forwards = _views.total
- - ranges::count(
- _views.list,
- 0,
- [](const StoryView &view) {
- return view.repostId
- ? view.repostId
- : view.forwardId.bare;
- });
- }
- }
- const auto known = int(_views.list.size());
- if (known >= _recentViewers.size()) {
- const auto take = std::min(known, kRecentViewersMax);
- auto viewers = _views.list
- | ranges::views::take(take)
- | ranges::views::transform(&StoryView::peer)
- | ranges::to_vector;
- if (_recentViewers != viewers) {
- _recentViewers = std::move(viewers);
- if (!changed) {
- // Count not changed, but list of recent viewers changed.
- _peer->session().changes().storyUpdated(
- this,
- UpdateFlag::ViewsChanged);
- }
- }
- }
- if (changed) {
- _peer->session().changes().storyUpdated(
- this,
- UpdateFlag::ViewsChanged);
- }
- }
- void Story::applyChannelReactionsSlice(
- const QString &offset,
- const StoryViews &slice) {
- const auto changed = (_channelReactions.reactions != slice.reactions)
- || (_channelReactions.total != slice.total);
- _channelReactions.reactions = slice.reactions;
- _channelReactions.total = slice.total;
- _channelReactions.known = true;
- if (offset.isEmpty()) {
- _channelReactions = slice;
- } else if (_channelReactions.nextOffset == offset) {
- _channelReactions.list.insert(
- end(_channelReactions.list),
- begin(slice.list),
- end(slice.list));
- _channelReactions.nextOffset = slice.nextOffset;
- if (_channelReactions.nextOffset.isEmpty()) {
- _channelReactions.total = int(_channelReactions.list.size());
- }
- }
- if (changed) {
- _peer->session().changes().storyUpdated(
- this,
- UpdateFlag::ViewsChanged);
- }
- }
- const std::vector<StoryLocation> &Story::locations() const {
- return _locations;
- }
- const std::vector<SuggestedReaction> &Story::suggestedReactions() const {
- return _suggestedReactions;
- }
- const std::vector<ChannelPost> &Story::channelPosts() const {
- return _channelPosts;
- }
- const std::vector<UrlArea> &Story::urlAreas() const {
- return _urlAreas;
- }
- const std::vector<WeatherArea> &Story::weatherAreas() const {
- return _weatherAreas;
- }
- void Story::applyChanges(
- StoryMedia media,
- const MTPDstoryItem &data,
- TimeId now) {
- applyFields(std::move(media), data, now, false);
- }
- Story::ViewsCounts Story::parseViewsCounts(
- const MTPDstoryViews &data,
- const Data::ReactionId &mine) {
- auto result = ViewsCounts{
- .views = data.vviews_count().v,
- .forwards = data.vforwards_count().value_or_empty(),
- .reactions = data.vreactions_count().value_or_empty(),
- };
- if (const auto list = data.vrecent_viewers()) {
- result.viewers.reserve(list->v.size());
- auto &owner = _peer->owner();
- auto &&cut = list->v
- | ranges::views::take(kRecentViewersMax);
- for (const auto &id : cut) {
- result.viewers.push_back(owner.peer(peerFromUser(id)));
- }
- }
- auto total = 0;
- if (const auto list = data.vreactions()
- ; list && _peer->isChannel()) {
- result.reactionsCounts.reserve(list->v.size());
- for (const auto &reaction : list->v) {
- const auto &data = reaction.data();
- const auto id = Data::ReactionFromMTP(data.vreaction());
- const auto count = data.vcount().v;
- result.reactionsCounts[id] = count;
- total += count;
- }
- }
- if (!mine.empty()) {
- if (auto &count = result.reactionsCounts[mine]; !count) {
- count = 1;
- ++total;
- }
- }
- if (result.reactions < total) {
- result.reactions = total;
- }
- return result;
- }
- void Story::applyFields(
- StoryMedia media,
- const MTPDstoryItem &data,
- TimeId now,
- bool initial) {
- _lastUpdateTime = now;
- const auto reaction = data.is_min()
- ? _sentReactionId
- : data.vsent_reaction()
- ? Data::ReactionFromMTP(*data.vsent_reaction())
- : Data::ReactionId();
- const auto inProfile = data.is_pinned();
- const auto edited = data.is_edited();
- const auto privacy = data.is_public()
- ? StoryPrivacy::Public
- : data.is_close_friends()
- ? StoryPrivacy::CloseFriends
- : data.is_contacts()
- ? StoryPrivacy::Contacts
- : data.is_selected_contacts()
- ? StoryPrivacy::SelectedContacts
- : StoryPrivacy::Other;
- const auto noForwards = data.is_noforwards();
- const auto out = data.is_min() ? _out : data.is_out();
- auto caption = TextWithEntities{
- data.vcaption().value_or_empty(),
- Api::EntitiesFromMTP(
- &owner().session(),
- data.ventities().value_or_empty()),
- };
- if (const auto user = _peer->asUser()) {
- if (!user->isVerified() && !user->isPremium()) {
- caption = StripLinks(std::move(caption));
- }
- }
- auto counts = ViewsCounts();
- auto viewsKnown = _views.known;
- if (const auto info = data.vviews()) {
- counts = parseViewsCounts(info->data(), reaction);
- viewsKnown = true;
- } else {
- counts.views = _views.total;
- counts.forwards = _views.forwards;
- counts.reactions = _views.reactions;
- counts.viewers = _recentViewers;
- for (const auto &suggested : _suggestedReactions) {
- if (const auto count = suggested.count) {
- counts.reactionsCounts[suggested.reaction] = count;
- }
- }
- }
- auto locations = std::vector<StoryLocation>();
- auto suggestedReactions = std::vector<SuggestedReaction>();
- auto channelPosts = std::vector<ChannelPost>();
- auto urlAreas = std::vector<UrlArea>();
- auto weatherAreas = std::vector<WeatherArea>();
- if (const auto areas = data.vmedia_areas()) {
- for (const auto &area : areas->v) {
- if (const auto location = ParseLocation(area)) {
- locations.push_back(*location);
- } else if (auto reaction = ParseSuggestedReaction(area)) {
- const auto i = counts.reactionsCounts.find(
- reaction->reaction);
- if (i != end(counts.reactionsCounts)) {
- reaction->count = i->second;
- }
- suggestedReactions.push_back(*reaction);
- } else if (auto post = ParseChannelPost(area)) {
- channelPosts.push_back(*post);
- } else if (auto url = ParseUrlArea(area)) {
- urlAreas.push_back(*url);
- } else if (auto weather = ParseWeatherArea(area)) {
- weatherAreas.push_back(*weather);
- }
- }
- }
- const auto inProfileChanged = (_inProfile != inProfile);
- const auto editedChanged = (_edited != edited);
- const auto mediaChanged = (_media != media);
- const auto captionChanged = (_caption != caption);
- const auto locationsChanged = (_locations != locations);
- const auto suggestedReactionsChanged
- = (_suggestedReactions != suggestedReactions);
- const auto channelPostsChanged = (_channelPosts != channelPosts);
- const auto urlAreasChanged = (_urlAreas != urlAreas);
- const auto weatherAreasChanged = (_weatherAreas != weatherAreas);
- const auto reactionChanged = (_sentReactionId != reaction);
- _out = out;
- _privacyPublic = (privacy == StoryPrivacy::Public);
- _privacyCloseFriends = (privacy == StoryPrivacy::CloseFriends);
- _privacyContacts = (privacy == StoryPrivacy::Contacts);
- _privacySelectedContacts = (privacy == StoryPrivacy::SelectedContacts);
- _edited = edited;
- _inProfile = inProfile;
- _noForwards = noForwards;
- if (mediaChanged) {
- _media = std::move(media);
- }
- if (captionChanged) {
- _caption = std::move(caption);
- }
- if (locationsChanged) {
- _locations = std::move(locations);
- }
- if (suggestedReactionsChanged) {
- _suggestedReactions = std::move(suggestedReactions);
- }
- if (channelPostsChanged) {
- _channelPosts = std::move(channelPosts);
- }
- if (urlAreasChanged) {
- _urlAreas = std::move(urlAreas);
- }
- if (weatherAreasChanged) {
- _weatherAreas = std::move(weatherAreas);
- }
- if (reactionChanged) {
- _sentReactionId = reaction;
- }
- updateViewsCounts(std::move(counts), viewsKnown, initial);
- const auto changed = editedChanged
- || captionChanged
- || mediaChanged
- || locationsChanged
- || channelPostsChanged
- || urlAreasChanged
- || weatherAreasChanged;
- const auto reactionsChanged = reactionChanged
- || suggestedReactionsChanged;
- if (!initial && (changed || reactionsChanged)) {
- _peer->session().changes().storyUpdated(this, UpdateFlag()
- | (changed ? UpdateFlag::Edited : UpdateFlag())
- | (reactionsChanged ? UpdateFlag::Reaction : UpdateFlag()));
- }
- if (!initial && (captionChanged || mediaChanged)) {
- if (const auto item = _peer->owner().stories().lookupItem(this)) {
- item->applyChanges(this);
- }
- _peer->owner().refreshStoryItemViews(fullId());
- }
- if (inProfileChanged) {
- _peer->owner().stories().savedStateChanged(this);
- }
- }
- void Story::updateViewsCounts(ViewsCounts &&counts, bool known, bool initial) {
- const auto total = _views.total
- ? _views.total
- : (counts.views + counts.forwards);
- const auto viewsChanged = (_views.total != total)
- || (_views.forwards != counts.forwards)
- || (_views.reactions != counts.reactions)
- || (_recentViewers != counts.viewers);
- if (_views.reactions != counts.reactions
- || _views.forwards != counts.forwards
- || _views.total != total
- || _views.known != known) {
- _views = StoryViews{
- .reactions = counts.reactions,
- .forwards = counts.forwards,
- .views = counts.views,
- .total = total,
- .known = known,
- };
- if (!_channelReactions.total) {
- _channelReactions.total = _views.reactions + _views.forwards;
- }
- }
- if (viewsChanged) {
- _recentViewers = std::move(counts.viewers);
- _peer->session().changes().storyUpdated(
- this,
- UpdateFlag::ViewsChanged);
- }
- }
- void Story::applyViewsCounts(const MTPDstoryViews &data) {
- auto counts = parseViewsCounts(data, _sentReactionId);
- auto suggestedCountsChanged = false;
- for (auto &suggested : _suggestedReactions) {
- const auto i = counts.reactionsCounts.find(suggested.reaction);
- const auto v = (i != end(counts.reactionsCounts)) ? i->second : 0;
- if (suggested.count != v) {
- suggested.count = v;
- suggestedCountsChanged = true;
- }
- }
- updateViewsCounts(std::move(counts), true, false);
- if (suggestedCountsChanged) {
- _peer->session().changes().storyUpdated(this, UpdateFlag::Reaction);
- }
- }
- TimeId Story::lastUpdateTime() const {
- return _lastUpdateTime;
- }
- bool Story::repost() const {
- return _repostSourcePeer || !_repostSourceName.isEmpty();
- }
- bool Story::repostModified() const {
- return _repostModified;
- }
- PeerData *Story::repostSourcePeer() const {
- return _repostSourcePeer;
- }
- QString Story::repostSourceName() const {
- return _repostSourceName;
- }
- StoryId Story::repostSourceId() const {
- return _repostSourceId;
- }
- PeerData *Story::fromPeer() const {
- return _fromPeer;
- }
- StoryPreload::StoryPreload(not_null<Story*> story, Fn<void()> done)
- : _story(story) {
- if (const auto photo = _story->photo()) {
- if (PhotoPreload::Should(photo, story->peer())) {
- _task = std::make_unique<PhotoPreload>(
- photo,
- story->fullId(),
- std::move(done));
- } else {
- done();
- }
- } else if (const auto video = _story->document()) {
- if (VideoPreload::Can(video)) {
- _task = std::make_unique<VideoPreload>(
- video,
- story->fullId(),
- std::move(done));
- } else {
- done();
- }
- } else {
- done();
- }
- }
- StoryPreload::~StoryPreload() = default;
- FullStoryId StoryPreload::id() const {
- return _story->fullId();
- }
- not_null<Story*> StoryPreload::story() const {
- return _story;
- }
- } // namespace Data
|