"Fossies" - the Fresh Open Source Software Archive

Member "tdesktop-2.6.1/Telegram/SourceFiles/history/history_item.cpp" (24 Feb 2021, 29399 Bytes) of package /linux/misc/tdesktop-2.6.1.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) C and C++ source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. For more information about "history_item.cpp" see the Fossies "Dox" file reference documentation and the last Fossies "Diffs" side-by-side code changes report: 2.5.9_vs_2.6.0.

    1 /*
    2 This file is part of Telegram Desktop,
    3 the official desktop application for the Telegram messaging service.
    4 
    5 For license and copyright information please follow this link:
    6 https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
    7 */
    8 #include "history/history_item.h"
    9 
   10 #include "lang/lang_keys.h"
   11 #include "mainwidget.h"
   12 #include "layout.h"
   13 #include "history/view/history_view_element.h"
   14 #include "history/view/history_view_service_message.h"
   15 #include "history/history_item_components.h"
   16 #include "history/view/media/history_view_media_grouped.h"
   17 #include "history/history_service.h"
   18 #include "history/history_message.h"
   19 #include "history/history.h"
   20 #include "mtproto/mtproto_config.h"
   21 #include "media/clip/media_clip_reader.h"
   22 #include "ui/effects/ripple_animation.h"
   23 #include "ui/text/text_isolated_emoji.h"
   24 #include "ui/text/text_options.h"
   25 #include "storage/file_upload.h"
   26 #include "storage/storage_facade.h"
   27 #include "storage/storage_shared_media.h"
   28 //#include "storage/storage_feed_messages.h" // #feed
   29 #include "main/main_session.h"
   30 #include "apiwrap.h"
   31 #include "media/audio/media_audio.h"
   32 #include "core/application.h"
   33 #include "mainwindow.h"
   34 #include "window/window_session_controller.h"
   35 #include "core/crash_reports.h"
   36 #include "base/unixtime.h"
   37 #include "api/api_text_entities.h"
   38 #include "data/data_scheduled_messages.h" // kScheduledUntilOnlineTimestamp
   39 #include "data/data_changes.h"
   40 #include "data/data_session.h"
   41 #include "data/data_messages.h"
   42 #include "data/data_media_types.h"
   43 #include "data/data_folder.h"
   44 #include "data/data_channel.h"
   45 #include "data/data_chat.h"
   46 #include "data/data_user.h"
   47 #include "styles/style_dialogs.h"
   48 #include "styles/style_chat.h"
   49 
   50 namespace {
   51 
   52 constexpr auto kNotificationTextLimit = 255;
   53 
   54 enum class MediaCheckResult {
   55     Good,
   56     Unsupported,
   57     Empty,
   58     HasTimeToLive,
   59 };
   60 
   61 not_null<HistoryItem*> CreateUnsupportedMessage(
   62         not_null<History*> history,
   63         MsgId msgId,
   64         MTPDmessage::Flags flags,
   65         MTPDmessage_ClientFlags clientFlags,
   66         MsgId replyTo,
   67         UserId viaBotId,
   68         TimeId date,
   69         PeerId from) {
   70     const auto siteLink = qsl("https://desktop.telegram.org");
   71     auto text = TextWithEntities{
   72         tr::lng_message_unsupported(tr::now, lt_link, siteLink)
   73     };
   74     TextUtilities::ParseEntities(text, Ui::ItemTextNoMonoOptions().flags);
   75     text.entities.push_front(
   76         EntityInText(EntityType::Italic, 0, text.text.size()));
   77     flags &= ~MTPDmessage::Flag::f_post_author;
   78     flags |= MTPDmessage::Flag::f_legacy;
   79     return history->makeMessage(
   80         msgId,
   81         flags,
   82         clientFlags,
   83         replyTo,
   84         viaBotId,
   85         date,
   86         from,
   87         QString(),
   88         text);
   89 }
   90 
   91 MediaCheckResult CheckMessageMedia(const MTPMessageMedia &media) {
   92     using Result = MediaCheckResult;
   93     return media.match([](const MTPDmessageMediaEmpty &) {
   94         return Result::Good;
   95     }, [](const MTPDmessageMediaContact &) {
   96         return Result::Good;
   97     }, [](const MTPDmessageMediaGeo &data) {
   98         return data.vgeo().match([](const MTPDgeoPoint &) {
   99             return Result::Good;
  100         }, [](const MTPDgeoPointEmpty &) {
  101             return Result::Empty;
  102         });
  103     }, [](const MTPDmessageMediaVenue &data) {
  104         return data.vgeo().match([](const MTPDgeoPoint &) {
  105             return Result::Good;
  106         }, [](const MTPDgeoPointEmpty &) {
  107             return Result::Empty;
  108         });
  109     }, [](const MTPDmessageMediaGeoLive &data) {
  110         return data.vgeo().match([](const MTPDgeoPoint &) {
  111             return Result::Good;
  112         }, [](const MTPDgeoPointEmpty &) {
  113             return Result::Empty;
  114         });
  115     }, [](const MTPDmessageMediaPhoto &data) {
  116         const auto photo = data.vphoto();
  117         if (data.vttl_seconds()) {
  118             return Result::HasTimeToLive;
  119         } else if (!photo) {
  120             return Result::Empty;
  121         }
  122         return photo->match([](const MTPDphoto &) {
  123             return Result::Good;
  124         }, [](const MTPDphotoEmpty &) {
  125             return Result::Empty;
  126         });
  127     }, [](const MTPDmessageMediaDocument &data) {
  128         const auto document = data.vdocument();
  129         if (data.vttl_seconds()) {
  130             return Result::HasTimeToLive;
  131         } else if (!document) {
  132             return Result::Empty;
  133         }
  134         return document->match([](const MTPDdocument &) {
  135             return Result::Good;
  136         }, [](const MTPDdocumentEmpty &) {
  137             return Result::Empty;
  138         });
  139     }, [](const MTPDmessageMediaWebPage &data) {
  140         return data.vwebpage().match([](const MTPDwebPage &) {
  141             return Result::Good;
  142         }, [](const MTPDwebPageEmpty &) {
  143             return Result::Good;
  144         }, [](const MTPDwebPagePending &) {
  145             return Result::Good;
  146         }, [](const MTPDwebPageNotModified &) {
  147             return Result::Unsupported;
  148         });
  149     }, [](const MTPDmessageMediaGame &data) {
  150         return data.vgame().match([](const MTPDgame &) {
  151             return Result::Good;
  152         });
  153     }, [](const MTPDmessageMediaInvoice &) {
  154         return Result::Good;
  155     }, [](const MTPDmessageMediaPoll &) {
  156         return Result::Good;
  157     }, [](const MTPDmessageMediaDice &) {
  158         return Result::Good;
  159     }, [](const MTPDmessageMediaUnsupported &) {
  160         return Result::Unsupported;
  161     });
  162 }
  163 
  164 } // namespace
  165 
  166 void HistoryItem::HistoryItem::Destroyer::operator()(HistoryItem *value) {
  167     if (value) {
  168         value->destroy();
  169     }
  170 }
  171 
  172 HistoryItem::HistoryItem(
  173     not_null<History*> history,
  174     MsgId id,
  175     MTPDmessage::Flags flags,
  176     MTPDmessage_ClientFlags clientFlags,
  177     TimeId date,
  178     PeerId from)
  179 : id(id)
  180 , _history(history)
  181 , _from(from ? history->owner().peer(from) : history->peer)
  182 , _flags(flags)
  183 , _clientFlags(clientFlags)
  184 , _date(date) {
  185     if (isHistoryEntry() && IsClientMsgId(id)) {
  186         _history->registerLocalMessage(this);
  187     }
  188 }
  189 
  190 TimeId HistoryItem::date() const {
  191     return _date;
  192 }
  193 
  194 TimeId HistoryItem::NewMessageDate(TimeId scheduled) {
  195     return scheduled ? scheduled : base::unixtime::now();
  196 }
  197 
  198 void HistoryItem::applyServiceDateEdition(const MTPDmessageService &data) {
  199     const auto date = data.vdate().v;
  200     if (_date == date) {
  201         return;
  202     }
  203     _date = date;
  204 }
  205 
  206 void HistoryItem::finishEdition(int oldKeyboardTop) {
  207     _history->owner().requestItemViewRefresh(this);
  208     invalidateChatListEntry();
  209     if (const auto group = _history->owner().groups().find(this)) {
  210         const auto leader = group->items.front();
  211         if (leader != this) {
  212             _history->owner().requestItemViewRefresh(leader);
  213             leader->invalidateChatListEntry();
  214         }
  215     }
  216 
  217     // Should be completely redesigned as the oldTop no longer exists.
  218     //if (oldKeyboardTop >= 0) { // #TODO edit bot message
  219     //  if (auto keyboard = Get<HistoryMessageReplyMarkup>()) {
  220     //      keyboard->oldTop = oldKeyboardTop;
  221     //  }
  222     //}
  223 
  224     _history->owner().updateDependentMessages(this);
  225 }
  226 
  227 void HistoryItem::setGroupId(MessageGroupId groupId) {
  228     Expects(!_groupId);
  229 
  230     _groupId = groupId;
  231     _history->owner().groups().registerMessage(this);
  232 }
  233 
  234 HistoryMessageReplyMarkup *HistoryItem::inlineReplyMarkup() {
  235     if (const auto markup = Get<HistoryMessageReplyMarkup>()) {
  236         if (markup->flags & MTPDreplyKeyboardMarkup_ClientFlag::f_inline) {
  237             return markup;
  238         }
  239     }
  240     return nullptr;
  241 }
  242 
  243 ReplyKeyboard *HistoryItem::inlineReplyKeyboard() {
  244     if (const auto markup = inlineReplyMarkup()) {
  245         return markup->inlineKeyboard.get();
  246     }
  247     return nullptr;
  248 }
  249 
  250 ChannelData *HistoryItem::discussionPostOriginalSender() const {
  251     if (!history()->peer->isMegagroup()) {
  252         return nullptr;
  253     }
  254     if (const auto forwarded = Get<HistoryMessageForwarded>()) {
  255         const auto from = forwarded->savedFromPeer;
  256         if (const auto result = from ? from->asChannel() : nullptr) {
  257             return result;
  258         }
  259     }
  260     return nullptr;
  261 }
  262 
  263 bool HistoryItem::isDiscussionPost() const {
  264     return (discussionPostOriginalSender() != nullptr);
  265 }
  266 
  267 HistoryItem *HistoryItem::lookupDiscussionPostOriginal() const {
  268     if (!history()->peer->isMegagroup()) {
  269         return nullptr;
  270     }
  271     const auto forwarded = Get<HistoryMessageForwarded>();
  272     if (!forwarded
  273         || !forwarded->savedFromPeer
  274         || !forwarded->savedFromMsgId) {
  275         return nullptr;
  276     }
  277     return _history->owner().message(
  278         forwarded->savedFromPeer->asChannel(),
  279         forwarded->savedFromMsgId);
  280 }
  281 
  282 PeerData *HistoryItem::displayFrom() const {
  283     if (const auto sender = discussionPostOriginalSender()) {
  284         return sender;
  285     } else if (const auto forwarded = Get<HistoryMessageForwarded>()) {
  286         if (history()->peer->isSelf() || history()->peer->isRepliesChat() || forwarded->imported) {
  287             return forwarded->originalSender;
  288         }
  289     }
  290     return author().get();
  291 }
  292 
  293 void HistoryItem::invalidateChatListEntry() {
  294     history()->session().changes().messageUpdated(
  295         this,
  296         Data::MessageUpdate::Flag::DialogRowRefresh);
  297 
  298     // invalidate cache for drawInDialog
  299     if (history()->textCachedFor == this) {
  300         history()->textCachedFor = nullptr;
  301     }
  302     //if (const auto feed = history()->peer->feed()) { // #TODO archive
  303     //  if (feed->textCachedFor == this) {
  304     //      feed->textCachedFor = nullptr;
  305     //      feed->updateChatListEntry();
  306     //  }
  307     //}
  308 }
  309 
  310 void HistoryItem::finishEditionToEmpty() {
  311     finishEdition(-1);
  312     _history->itemVanished(this);
  313 }
  314 
  315 bool HistoryItem::hasUnreadMediaFlag() const {
  316     if (_history->peer->isChannel()) {
  317         const auto passed = base::unixtime::now() - date();
  318         const auto &config = _history->session().serverConfig();
  319         if (passed >= config.channelsReadMediaPeriod) {
  320             return false;
  321         }
  322     }
  323     return _flags & MTPDmessage::Flag::f_media_unread;
  324 }
  325 
  326 bool HistoryItem::isUnreadMention() const {
  327     return mentionsMe() && (_flags & MTPDmessage::Flag::f_media_unread);
  328 }
  329 
  330 bool HistoryItem::mentionsMe() const {
  331     if (Has<HistoryServicePinned>()
  332         && !Core::App().settings().notifyAboutPinned()) {
  333         return false;
  334     }
  335     return _flags & MTPDmessage::Flag::f_mentioned;
  336 }
  337 
  338 bool HistoryItem::isUnreadMedia() const {
  339     if (!hasUnreadMediaFlag()) {
  340         return false;
  341     } else if (const auto media = this->media()) {
  342         if (const auto document = media->document()) {
  343             if (document->isVoiceMessage() || document->isVideoMessage()) {
  344                 return (media->webpage() == nullptr);
  345             }
  346         }
  347     }
  348     return false;
  349 }
  350 
  351 void HistoryItem::markMediaRead() {
  352     _flags &= ~MTPDmessage::Flag::f_media_unread;
  353 
  354     if (mentionsMe()) {
  355         history()->updateChatListEntry();
  356         history()->eraseFromUnreadMentions(id);
  357     }
  358 }
  359 
  360 void HistoryItem::setIsPinned(bool pinned) {
  361     const auto changed = (isPinned() != pinned);
  362     if (pinned) {
  363         _flags |= MTPDmessage::Flag::f_pinned;
  364         history()->session().storage().add(Storage::SharedMediaAddExisting(
  365             history()->peer->id,
  366             Storage::SharedMediaType::Pinned,
  367             id,
  368             { id, id }));
  369         history()->peer->setHasPinnedMessages(true);
  370     } else {
  371         _flags &= ~MTPDmessage::Flag::f_pinned;
  372         history()->session().storage().remove(Storage::SharedMediaRemoveOne(
  373             history()->peer->id,
  374             Storage::SharedMediaType::Pinned,
  375             id));
  376     }
  377     if (changed) {
  378         history()->owner().requestItemResize(this);
  379     }
  380 }
  381 
  382 bool HistoryItem::definesReplyKeyboard() const {
  383     if (const auto markup = Get<HistoryMessageReplyMarkup>()) {
  384         if (markup->flags & MTPDreplyKeyboardMarkup_ClientFlag::f_inline) {
  385             return false;
  386         }
  387         return true;
  388     }
  389 
  390     // optimization: don't create markup component for the case
  391     // MTPDreplyKeyboardHide with flags = 0, assume it has f_zero flag
  392     return (_flags & MTPDmessage::Flag::f_reply_markup);
  393 }
  394 
  395 MTPDreplyKeyboardMarkup::Flags HistoryItem::replyKeyboardFlags() const {
  396     Expects(definesReplyKeyboard());
  397 
  398     if (const auto markup = Get<HistoryMessageReplyMarkup>()) {
  399         return markup->flags;
  400     }
  401 
  402     // optimization: don't create markup component for the case
  403     // MTPDreplyKeyboardHide with flags = 0, assume it has f_zero flag
  404     return MTPDreplyKeyboardMarkup_ClientFlag::f_zero | 0;
  405 }
  406 
  407 void HistoryItem::addLogEntryOriginal(
  408         WebPageId localId,
  409         const QString &label,
  410         const TextWithEntities &content) {
  411     Expects(isAdminLogEntry());
  412 
  413     AddComponents(HistoryMessageLogEntryOriginal::Bit());
  414     Get<HistoryMessageLogEntryOriginal>()->page = _history->owner().webpage(
  415         localId,
  416         label,
  417         content);
  418 }
  419 
  420 PeerData *HistoryItem::specialNotificationPeer() const {
  421     return (mentionsMe() && !_history->peer->isUser())
  422         ? from().get()
  423         : nullptr;
  424 }
  425 
  426 UserData *HistoryItem::viaBot() const {
  427     if (const auto via = Get<HistoryMessageVia>()) {
  428         return via->bot;
  429     }
  430     return nullptr;
  431 }
  432 
  433 UserData *HistoryItem::getMessageBot() const {
  434     if (const auto bot = viaBot()) {
  435         return bot;
  436     }
  437     auto bot = from()->asUser();
  438     if (!bot) {
  439         bot = history()->peer->asUser();
  440     }
  441     return (bot && bot->isBot()) ? bot : nullptr;
  442 }
  443 
  444 bool HistoryItem::isHistoryEntry() const {
  445     return IsServerMsgId(id)
  446         || (_clientFlags & MTPDmessage_ClientFlag::f_local_history_entry);
  447 }
  448 
  449 bool HistoryItem::isAdminLogEntry() const {
  450     return (_clientFlags & MTPDmessage_ClientFlag::f_admin_log_entry);
  451 }
  452 
  453 bool HistoryItem::isFromScheduled() const {
  454     return isHistoryEntry()
  455         && (_flags & MTPDmessage::Flag::f_from_scheduled);
  456 }
  457 
  458 bool HistoryItem::isScheduled() const {
  459     return !isHistoryEntry()
  460         && !isAdminLogEntry()
  461         && (_flags & MTPDmessage::Flag::f_from_scheduled);
  462 }
  463 
  464 void HistoryItem::destroy() {
  465     _history->destroyMessage(this);
  466 }
  467 
  468 void HistoryItem::refreshMainView() {
  469     if (const auto view = mainView()) {
  470         _history->owner().notifyHistoryChangeDelayed(_history);
  471         view->refreshInBlock();
  472     }
  473 }
  474 
  475 void HistoryItem::removeMainView() {
  476     if (const auto view = mainView()) {
  477         _history->owner().notifyHistoryChangeDelayed(_history);
  478         view->removeFromBlock();
  479     }
  480 }
  481 
  482 void HistoryItem::clearMainView() {
  483     _mainView = nullptr;
  484 }
  485 
  486 void HistoryItem::addToUnreadMentions(UnreadMentionType type) {
  487 }
  488 
  489 void HistoryItem::applyEditionToHistoryCleared() {
  490     applyEdition(
  491         MTP_messageService(
  492             MTP_flags(0),
  493             MTP_int(id),
  494             peerToMTP(PeerId(0)), // from_id
  495             peerToMTP(history()->peer->id),
  496             MTPMessageReplyHeader(),
  497             MTP_int(date()),
  498             MTP_messageActionHistoryClear(),
  499             MTPint() // ttl_period
  500         ).c_messageService());
  501 }
  502 
  503 void HistoryItem::applySentMessage(const MTPDmessage &data) {
  504     updateSentContent({
  505         qs(data.vmessage()),
  506         Api::EntitiesFromMTP(
  507             &history()->session(),
  508             data.ventities().value_or_empty())
  509     }, data.vmedia());
  510     updateReplyMarkup(data.vreply_markup());
  511     updateForwardedInfo(data.vfwd_from());
  512     setViewsCount(data.vviews().value_or(-1));
  513     if (const auto replies = data.vreplies()) {
  514         setReplies(*replies);
  515     } else {
  516         clearReplies();
  517     }
  518     setForwardsCount(data.vforwards().value_or(-1));
  519     if (const auto reply = data.vreply_to()) {
  520         reply->match([&](const MTPDmessageReplyHeader &data) {
  521             setReplyToTop(
  522                 data.vreply_to_top_id().value_or(
  523                     data.vreply_to_msg_id().v));
  524         });
  525     }
  526     setPostAuthor(data.vpost_author().value_or_empty());
  527     contributeToSlowmode(data.vdate().v);
  528     indexAsNewItem();
  529     history()->owner().requestItemTextRefresh(this);
  530     history()->owner().updateDependentMessages(this);
  531 }
  532 
  533 void HistoryItem::applySentMessage(
  534         const QString &text,
  535         const MTPDupdateShortSentMessage &data,
  536         bool wasAlready) {
  537     updateSentContent({
  538         text,
  539         Api::EntitiesFromMTP(
  540             &history()->session(),
  541             data.ventities().value_or_empty())
  542         }, data.vmedia());
  543     contributeToSlowmode(data.vdate().v);
  544     if (!wasAlready) {
  545         indexAsNewItem();
  546     }
  547 }
  548 
  549 void HistoryItem::indexAsNewItem() {
  550     if (IsServerMsgId(id)) {
  551         addToUnreadMentions(UnreadMentionType::New);
  552         if (const auto types = sharedMediaTypes()) {
  553             _history->session().storage().add(Storage::SharedMediaAddNew(
  554                 _history->peer->id,
  555                 types,
  556                 id));
  557             if (types.test(Storage::SharedMediaType::Pinned)) {
  558                 _history->peer->setHasPinnedMessages(true);
  559             }
  560         }
  561         //if (const auto channel = history()->peer->asChannel()) { // #feed
  562         //  if (const auto feed = channel->feed()) {
  563         //      _history->session().storage().add(Storage::FeedMessagesAddNew(
  564         //          feed->id(),
  565         //          position()));
  566         //  }
  567         //}
  568     }
  569 }
  570 
  571 void HistoryItem::setRealId(MsgId newId) {
  572     Expects(_clientFlags & MTPDmessage_ClientFlag::f_sending);
  573     Expects(IsClientMsgId(id));
  574 
  575     const auto oldId = std::exchange(id, newId);
  576     _clientFlags &= ~MTPDmessage_ClientFlag::f_sending;
  577     if (IsServerMsgId(id)) {
  578         _history->unregisterLocalMessage(this);
  579     }
  580     _history->owner().notifyItemIdChange({ this, oldId });
  581 
  582     // We don't fire MessageUpdate::Flag::ReplyMarkup and update keyboard
  583     // in history widget, because it can't exist for an outgoing message.
  584     // Only inline keyboards can be in outgoing messages.
  585     if (const auto markup = inlineReplyMarkup()) {
  586         if (markup->inlineKeyboard) {
  587             markup->inlineKeyboard->updateMessageId();
  588         }
  589     }
  590 
  591     _history->owner().requestItemRepaint(this);
  592 }
  593 
  594 bool HistoryItem::canPin() const {
  595     if (id < 0 || !toHistoryMessage()) {
  596         return false;
  597     } else if (const auto m = media(); m && m->call()) {
  598         return false;
  599     }
  600     return _history->peer->canPinMessages();
  601 }
  602 
  603 bool HistoryItem::allowsSendNow() const {
  604     return false;
  605 }
  606 
  607 bool HistoryItem::allowsForward() const {
  608     return false;
  609 }
  610 
  611 bool HistoryItem::allowsEdit(TimeId now) const {
  612     return false;
  613 }
  614 
  615 bool HistoryItem::canStopPoll() const {
  616     if (id < 0
  617         || Has<HistoryMessageVia>()
  618         || Has<HistoryMessageForwarded>()) {
  619         return false;
  620     }
  621 
  622     const auto peer = _history->peer;
  623     if (peer->isSelf()) {
  624         return true;
  625     } else if (const auto channel = peer->asChannel()) {
  626         if (isPost() && channel->canEditMessages()) {
  627             return true;
  628         } else if (out()) {
  629             return isPost() ? channel->canPublish() : channel->canWrite();
  630         } else {
  631             return false;
  632         }
  633     }
  634     return out();
  635 }
  636 
  637 bool HistoryItem::canDelete() const {
  638     if (!IsServerMsgId(id) && serviceMsg()) {
  639         return false;
  640     } else if (!isHistoryEntry() && !isScheduled()) {
  641         return false;
  642     }
  643     auto channel = _history->peer->asChannel();
  644     if (!channel) {
  645         return !isGroupMigrate();
  646     }
  647 
  648     if (id == 1) {
  649         return false;
  650     }
  651     if (channel->canDeleteMessages()) {
  652         return true;
  653     }
  654     if (out() && toHistoryMessage()) {
  655         return isPost() ? channel->canPublish() : true;
  656     }
  657     return false;
  658 }
  659 
  660 bool HistoryItem::canDeleteForEveryone(TimeId now) const {
  661     const auto peer = history()->peer;
  662     const auto &config = history()->session().serverConfig();
  663     const auto messageToMyself = peer->isSelf();
  664     const auto messageTooOld = messageToMyself
  665         ? false
  666         : peer->isUser()
  667         ? (now - date() >= config.revokePrivateTimeLimit)
  668         : (now - date() >= config.revokeTimeLimit);
  669     if (id < 0 || messageToMyself || messageTooOld || isPost()) {
  670         return false;
  671     }
  672     if (peer->isChannel()) {
  673         return false;
  674     } else if (const auto user = peer->asUser()) {
  675         // Bots receive all messages and there is no sense in revoking them.
  676         // See https://github.com/telegramdesktop/tdesktop/issues/3818
  677         if (user->isBot() && !user->isSupport()) {
  678             return false;
  679         }
  680     }
  681     if (const auto media = this->media()) {
  682         if (!media->allowsRevoke(now)) {
  683             return false;
  684         }
  685     }
  686     if (!out()) {
  687         if (const auto chat = peer->asChat()) {
  688             if (!chat->canDeleteMessages()) {
  689                 return false;
  690             }
  691         } else if (peer->isUser()) {
  692             return config.revokePrivateInbox;
  693         } else {
  694             return false;
  695         }
  696     }
  697     return true;
  698 }
  699 
  700 bool HistoryItem::suggestReport() const {
  701     if (out() || serviceMsg() || !IsServerMsgId(id)) {
  702         return false;
  703     } else if (const auto channel = history()->peer->asChannel()) {
  704         return true;
  705     } else if (const auto user = history()->peer->asUser()) {
  706         return user->isBot();
  707     }
  708     return false;
  709 }
  710 
  711 bool HistoryItem::suggestBanReport() const {
  712     auto channel = history()->peer->asChannel();
  713     auto fromUser = from()->asUser();
  714     if (!channel || !fromUser || !channel->canRestrictUser(fromUser)) {
  715         return false;
  716     }
  717     return !isPost() && !out();
  718 }
  719 
  720 bool HistoryItem::suggestDeleteAllReport() const {
  721     auto channel = history()->peer->asChannel();
  722     if (!channel || !channel->canDeleteMessages()) {
  723         return false;
  724     }
  725     return !isPost() && !out() && from()->isUser();
  726 }
  727 
  728 bool HistoryItem::hasDirectLink() const {
  729     return IsServerMsgId(id) && _history->peer->isChannel();
  730 }
  731 
  732 ChannelId HistoryItem::channelId() const {
  733     return _history->channelId();
  734 }
  735 
  736 Data::MessagePosition HistoryItem::position() const {
  737     return { .fullId = fullId(), .date = date() };
  738 }
  739 
  740 MsgId HistoryItem::replyToId() const {
  741     if (const auto reply = Get<HistoryMessageReply>()) {
  742         return reply->replyToId();
  743     }
  744     return 0;
  745 }
  746 
  747 MsgId HistoryItem::replyToTop() const {
  748     if (const auto reply = Get<HistoryMessageReply>()) {
  749         return reply->replyToTop();
  750     }
  751     return 0;
  752 }
  753 
  754 not_null<PeerData*> HistoryItem::author() const {
  755     return isPost() ? history()->peer : from();
  756 }
  757 
  758 TimeId HistoryItem::dateOriginal() const {
  759     if (const auto forwarded = Get<HistoryMessageForwarded>()) {
  760         return forwarded->originalDate;
  761     }
  762     return date();
  763 }
  764 
  765 PeerData *HistoryItem::senderOriginal() const {
  766     if (const auto forwarded = Get<HistoryMessageForwarded>()) {
  767         return forwarded->originalSender;
  768     }
  769     const auto peer = history()->peer;
  770     return (peer->isChannel() && !peer->isMegagroup()) ? peer : from();
  771 }
  772 
  773 const HiddenSenderInfo *HistoryItem::hiddenForwardedInfo() const {
  774     if (const auto forwarded = Get<HistoryMessageForwarded>()) {
  775         return forwarded->hiddenSenderInfo.get();
  776     }
  777     return nullptr;
  778 }
  779 
  780 not_null<PeerData*> HistoryItem::fromOriginal() const {
  781     if (const auto forwarded = Get<HistoryMessageForwarded>()) {
  782         if (forwarded->originalSender) {
  783             if (const auto user = forwarded->originalSender->asUser()) {
  784                 return user;
  785             }
  786         }
  787     }
  788     return from();
  789 }
  790 
  791 QString HistoryItem::authorOriginal() const {
  792     if (const auto forwarded = Get<HistoryMessageForwarded>()) {
  793         return forwarded->originalAuthor;
  794     } else if (const auto msgsigned = Get<HistoryMessageSigned>()) {
  795         if (!msgsigned->isAnonymousRank) {
  796             return msgsigned->author;
  797         }
  798     }
  799     return QString();
  800 }
  801 
  802 MsgId HistoryItem::idOriginal() const {
  803     if (const auto forwarded = Get<HistoryMessageForwarded>()) {
  804         return forwarded->originalId;
  805     }
  806     return id;
  807 }
  808 
  809 void HistoryItem::updateDate(TimeId newDate) {
  810     if (canUpdateDate() && _date != newDate) {
  811         _date = newDate;
  812         _history->owner().requestItemViewRefresh(this);
  813     }
  814 }
  815 
  816 bool HistoryItem::canUpdateDate() const {
  817     return isScheduled();
  818 }
  819 
  820 void HistoryItem::applyTTL(const MTPDmessage &data) {
  821     if (const auto period = data.vttl_period()) {
  822         if (period->v > 0) {
  823             applyTTL(data.vdate().v + period->v);
  824         }
  825     }
  826 }
  827 
  828 void HistoryItem::applyTTL(const MTPDmessageService &data) {
  829     if (const auto period = data.vttl_period()) {
  830         if (period->v > 0) {
  831             applyTTL(data.vdate().v + period->v);
  832         }
  833     }
  834 }
  835 
  836 void HistoryItem::applyTTL(TimeId destroyAt) {
  837     const auto previousDestroyAt = std::exchange(_ttlDestroyAt, destroyAt);
  838     if (previousDestroyAt) {
  839         history()->owner().unregisterMessageTTL(previousDestroyAt, this);
  840     }
  841     if (!_ttlDestroyAt) {
  842         return;
  843     } else if (base::unixtime::now() >= _ttlDestroyAt) {
  844         const auto session = &history()->session();
  845         crl::on_main(session, [session, id = fullId()]{
  846             if (const auto item = session->data().message(id)) {
  847                 item->destroy();
  848             }
  849         });
  850     } else {
  851         history()->owner().registerMessageTTL(_ttlDestroyAt, this);
  852     }
  853 }
  854 
  855 void HistoryItem::sendFailed() {
  856     Expects(_clientFlags & MTPDmessage_ClientFlag::f_sending);
  857     Expects(!(_clientFlags & MTPDmessage_ClientFlag::f_failed));
  858 
  859     _clientFlags = (_clientFlags | MTPDmessage_ClientFlag::f_failed)
  860         & ~MTPDmessage_ClientFlag::f_sending;
  861     history()->session().changes().historyUpdated(
  862         history(),
  863         Data::HistoryUpdate::Flag::LocalMessages);
  864 }
  865 
  866 bool HistoryItem::needCheck() const {
  867     return (out() && !isEmpty()) || (id < 0 && history()->peer->isSelf());
  868 }
  869 
  870 bool HistoryItem::unread() const {
  871     // Messages from myself are always read, unless scheduled.
  872     if (history()->peer->isSelf() && !isFromScheduled()) {
  873         return false;
  874     }
  875 
  876     if (out()) {
  877         // Outgoing messages in converted chats are always read.
  878         if (history()->peer->migrateTo()) {
  879             return false;
  880         }
  881 
  882         if (IsServerMsgId(id)) {
  883             if (!history()->isServerSideUnread(this)) {
  884                 return false;
  885             }
  886             if (const auto user = history()->peer->asUser()) {
  887                 if (user->isBot()) {
  888                     return false;
  889                 }
  890             } else if (const auto channel = history()->peer->asChannel()) {
  891                 if (!channel->isMegagroup()) {
  892                     return false;
  893                 }
  894             }
  895         }
  896         return true;
  897     }
  898 
  899     if (IsServerMsgId(id)) {
  900         if (!history()->isServerSideUnread(this)) {
  901             return false;
  902         }
  903         return true;
  904     }
  905     return (_clientFlags & MTPDmessage_ClientFlag::f_clientside_unread);
  906 }
  907 
  908 bool HistoryItem::showNotification() const {
  909     const auto channel = _history->peer->asChannel();
  910     if (channel && !channel->amIn()) {
  911         return false;
  912     }
  913     return (out() || _history->peer->isSelf())
  914         ? isFromScheduled()
  915         : unread();
  916 }
  917 
  918 void HistoryItem::markClientSideAsRead() {
  919     _clientFlags &= ~MTPDmessage_ClientFlag::f_clientside_unread;
  920 }
  921 
  922 MessageGroupId HistoryItem::groupId() const {
  923     return _groupId;
  924 }
  925 
  926 bool HistoryItem::isEmpty() const {
  927     return _text.isEmpty()
  928         && !_media
  929         && !Has<HistoryMessageLogEntryOriginal>();
  930 }
  931 
  932 QString HistoryItem::notificationText() const {
  933     const auto result = [&] {
  934         if (_media) {
  935             return _media->notificationText();
  936         } else if (!emptyText()) {
  937             return _text.toString();
  938         }
  939         return QString();
  940     }();
  941     return (result.size() <= kNotificationTextLimit)
  942         ? result
  943         : result.mid(0, kNotificationTextLimit) + qsl("...");
  944 }
  945 
  946 QString HistoryItem::inDialogsText(DrawInDialog way) const {
  947     auto getText = [this]() {
  948         if (_media) {
  949             if (_groupId) {
  950                 return textcmdLink(1, TextUtilities::Clean(tr::lng_in_dlg_album(tr::now)));
  951             }
  952             return _media->chatListText();
  953         } else if (!emptyText()) {
  954             return TextUtilities::Clean(_text.toString());
  955         }
  956         return QString();
  957     };
  958     const auto plainText = getText();
  959     const auto sender = [&]() -> PeerData* {
  960         if (isPost() || isEmpty() || (way == DrawInDialog::WithoutSender)) {
  961             return nullptr;
  962         } else if (!_history->peer->isUser() || out()) {
  963             return displayFrom();
  964         } else if (_history->peer->isSelf() && !Has<HistoryMessageForwarded>()) {
  965             return senderOriginal();
  966         }
  967         return nullptr;
  968     }();
  969     if (sender) {
  970         auto fromText = sender->isSelf() ? tr::lng_from_you(tr::now) : sender->shortName();
  971         auto fromWrapped = textcmdLink(1, tr::lng_dialogs_text_from_wrapped(tr::now, lt_from, TextUtilities::Clean(fromText)));
  972         return tr::lng_dialogs_text_with_from(tr::now, lt_from_part, fromWrapped, lt_message, plainText);
  973     }
  974     return plainText;
  975 }
  976 
  977 Ui::Text::IsolatedEmoji HistoryItem::isolatedEmoji() const {
  978     return Ui::Text::IsolatedEmoji();
  979 }
  980 
  981 void HistoryItem::drawInDialog(
  982         Painter &p,
  983         const QRect &r,
  984         bool active,
  985         bool selected,
  986         DrawInDialog way,
  987         const HistoryItem *&cacheFor,
  988         Ui::Text::String &cache) const {
  989     if (r.isEmpty()) {
  990         return;
  991     }
  992     if (cacheFor != this) {
  993         cacheFor = this;
  994         cache.setText(st::dialogsTextStyle, inDialogsText(way), Ui::DialogTextOptions());
  995     }
  996     p.setTextPalette(active ? st::dialogsTextPaletteActive : (selected ? st::dialogsTextPaletteOver : st::dialogsTextPalette));
  997     p.setFont(st::dialogsTextFont);
  998     p.setPen(active ? st::dialogsTextFgActive : (selected ? st::dialogsTextFgOver : st::dialogsTextFg));
  999     cache.drawElided(p, r.left(), r.top(), r.width(), r.height() / st::dialogsTextFont->height);
 1000     p.restoreTextPalette();
 1001 }
 1002 
 1003 HistoryItem::~HistoryItem() {
 1004     applyTTL(0);
 1005 }
 1006 
 1007 QDateTime ItemDateTime(not_null<const HistoryItem*> item) {
 1008     return base::unixtime::parse(item->date());
 1009 }
 1010 
 1011 QString ItemDateText(not_null<const HistoryItem*> item, bool isUntilOnline) {
 1012     const auto dateText = langDayOfMonthFull(ItemDateTime(item).date());
 1013     return !item->isScheduled()
 1014         ? dateText
 1015         : isUntilOnline
 1016             ? tr::lng_scheduled_date_until_online(tr::now)
 1017             : tr::lng_scheduled_date(tr::now, lt_date, dateText);
 1018 }
 1019 
 1020 bool IsItemScheduledUntilOnline(not_null<const HistoryItem*> item) {
 1021     return item->isScheduled()
 1022         && (item->date() ==
 1023             Data::ScheduledMessages::kScheduledUntilOnlineTimestamp);
 1024 }
 1025 
 1026 ClickHandlerPtr goToMessageClickHandler(
 1027         not_null<HistoryItem*> item,
 1028         FullMsgId returnToId) {
 1029     return goToMessageClickHandler(
 1030         item->history()->peer,
 1031         item->id,
 1032         returnToId);
 1033 }
 1034 
 1035 ClickHandlerPtr goToMessageClickHandler(
 1036         not_null<PeerData*> peer,
 1037         MsgId msgId,
 1038         FullMsgId returnToId) {
 1039     return std::make_shared<LambdaClickHandler>([=] {
 1040         if (const auto main = App::main()) { // multi good
 1041             if (&main->session() == &peer->session()) {
 1042                 auto params = Window::SectionShow{
 1043                     Window::SectionShow::Way::Forward
 1044                 };
 1045                 params.origin = Window::SectionShow::OriginMessage{
 1046                     returnToId
 1047                 };
 1048                 main->controller()->showPeerHistory(peer, params, msgId);
 1049             }
 1050         }
 1051     });
 1052 }
 1053 
 1054 not_null<HistoryItem*> HistoryItem::Create(
 1055         not_null<History*> history,
 1056         const MTPMessage &message,
 1057         MTPDmessage_ClientFlags clientFlags) {
 1058     return message.match([&](const MTPDmessage &data) -> HistoryItem* {
 1059         const auto media = data.vmedia();
 1060         const auto checked = media
 1061             ? CheckMessageMedia(*media)
 1062             : MediaCheckResult::Good;
 1063         if (checked == MediaCheckResult::Unsupported) {
 1064             return CreateUnsupportedMessage(
 1065                 history,
 1066                 data.vid().v,
 1067                 data.vflags().v,
 1068                 clientFlags,
 1069                 MsgId(0), // No need to pass reply_to data here.
 1070                 data.vvia_bot_id().value_or_empty(),
 1071                 data.vdate().v,
 1072                 data.vfrom_id() ? peerFromMTP(*data.vfrom_id()) : PeerId(0));
 1073         } else if (checked == MediaCheckResult::Empty) {
 1074             const auto text = HistoryService::PreparedText{
 1075                 tr::lng_message_empty(tr::now)
 1076             };
 1077             return history->makeServiceMessage(
 1078                 data.vid().v,
 1079                 clientFlags,
 1080                 data.vdate().v,
 1081                 text,
 1082                 data.vflags().v,
 1083                 data.vfrom_id() ? peerFromMTP(*data.vfrom_id()) : PeerId(0));
 1084         } else if (checked == MediaCheckResult::HasTimeToLive) {
 1085             return history->makeServiceMessage(data, clientFlags);
 1086         }
 1087         return history->makeMessage(data, clientFlags);
 1088     }, [&](const MTPDmessageService &data) -> HistoryItem* {
 1089         if (data.vaction().type() == mtpc_messageActionPhoneCall) {
 1090             return history->makeMessage(data, clientFlags);
 1091         }
 1092         return history->makeServiceMessage(data, clientFlags);
 1093     }, [&](const MTPDmessageEmpty &data) -> HistoryItem* {
 1094         const auto text = HistoryService::PreparedText{
 1095             tr::lng_message_empty(tr::now)
 1096         };
 1097         return history->makeServiceMessage(
 1098             data.vid().v,
 1099             clientFlags,
 1100             TimeId(0),
 1101             text);
 1102     });
 1103 }