diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index 95a70f8fb..8360eefeb 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -764,6 +764,8 @@ PRIVATE history/history_item_components.h history/history_item_edition.cpp history/history_item_edition.h + history/history_item_helpers.cpp + history/history_item_helpers.h history/history_item_reply_markup.cpp history/history_item_reply_markup.h history/history_item_text.cpp @@ -772,10 +774,6 @@ PRIVATE history/history_inner_widget.h history/history_location_manager.cpp history/history_location_manager.h - history/history_message.cpp - history/history_message.h - history/history_service.cpp - history/history_service.h history/history_unread_things.cpp history/history_unread_things.h history/history_view_highlight_manager.cpp diff --git a/Telegram/SourceFiles/api/api_polls.cpp b/Telegram/SourceFiles/api/api_polls.cpp index 85f34e2c2..d18c947c1 100644 --- a/Telegram/SourceFiles/api/api_polls.cpp +++ b/Telegram/SourceFiles/api/api_polls.cpp @@ -16,7 +16,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_poll.h" #include "data/data_session.h" #include "history/history.h" -#include "history/history_message.h" // ShouldSendSilent +#include "history/history_item.h" +#include "history/history_item_helpers.h" // ShouldSendSilent #include "main/main_session.h" namespace Api { diff --git a/Telegram/SourceFiles/api/api_sending.cpp b/Telegram/SourceFiles/api/api_sending.cpp index 917c6d002..b2004f70b 100644 --- a/Telegram/SourceFiles/api/api_sending.cpp +++ b/Telegram/SourceFiles/api/api_sending.cpp @@ -20,7 +20,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_changes.h" #include "data/stickers/data_stickers.h" #include "history/history.h" -#include "history/history_message.h" // NewMessageFlags. +#include "history/history_item.h" +#include "history/history_item_helpers.h" // NewMessageFlags. #include "chat_helpers/message_field.h" // ConvertTextTagsToEntities. #include "chat_helpers/stickers_dice_pack.h" // DicePacks::kDiceString. #include "ui/text/text_entity.h" // TextWithEntities. diff --git a/Telegram/SourceFiles/apiwrap.cpp b/Telegram/SourceFiles/apiwrap.cpp index 4231e5c9b..8ac060deb 100644 --- a/Telegram/SourceFiles/apiwrap.cpp +++ b/Telegram/SourceFiles/apiwrap.cpp @@ -67,8 +67,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "boxes/add_contact_box.h" #include "mtproto/mtproto_config.h" #include "history/history.h" -#include "history/history_message.h" +#include "history/history_item.h" #include "history/history_item_components.h" +#include "history/history_item_helpers.h" #include "main/main_session.h" #include "main/main_session_settings.h" #include "main/main_account.h" diff --git a/Telegram/SourceFiles/boxes/background_preview_box.cpp b/Telegram/SourceFiles/boxes/background_preview_box.cpp index 7e0be1f36..1a2d3fe24 100644 --- a/Telegram/SourceFiles/boxes/background_preview_box.cpp +++ b/Telegram/SourceFiles/boxes/background_preview_box.cpp @@ -20,7 +20,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/painter.h" #include "ui/ui_utility.h" #include "history/history.h" -#include "history/history_message.h" +#include "history/history_item.h" #include "history/view/history_view_message.h" #include "main/main_session.h" #include "apiwrap.h" diff --git a/Telegram/SourceFiles/boxes/peers/edit_peer_invite_link.cpp b/Telegram/SourceFiles/boxes/peers/edit_peer_invite_link.cpp index f9a24c72b..04c63816f 100644 --- a/Telegram/SourceFiles/boxes/peers/edit_peer_invite_link.cpp +++ b/Telegram/SourceFiles/boxes/peers/edit_peer_invite_link.cpp @@ -32,7 +32,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/painter.h" #include "boxes/share_box.h" #include "history/view/history_view_group_call_bar.h" // GenerateUserpics... -#include "history/history_message.h" // GetErrorTextForSending. +#include "history/history_item_helpers.h" // GetErrorTextForSending. #include "history/history.h" #include "ui/boxes/confirm_box.h" #include "boxes/peer_list_box.h" diff --git a/Telegram/SourceFiles/boxes/reactions_settings_box.cpp b/Telegram/SourceFiles/boxes/reactions_settings_box.cpp index ce617370b..19b42bb21 100644 --- a/Telegram/SourceFiles/boxes/reactions_settings_box.cpp +++ b/Telegram/SourceFiles/boxes/reactions_settings_box.cpp @@ -15,7 +15,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_session.h" #include "history/admin_log/history_admin_log_item.h" #include "history/history.h" -#include "history/history_message.h" +#include "history/history_item.h" #include "history/view/history_view_element.h" #include "history/view/reactions/history_view_reactions_strip.h" #include "lang/lang_keys.h" diff --git a/Telegram/SourceFiles/boxes/share_box.cpp b/Telegram/SourceFiles/boxes/share_box.cpp index e8c4b4987..3e8f33765 100644 --- a/Telegram/SourceFiles/boxes/share_box.cpp +++ b/Telegram/SourceFiles/boxes/share_box.cpp @@ -30,7 +30,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "menu/menu_check_item.h" #include "menu/menu_send.h" #include "history/history.h" -#include "history/history_message.h" +#include "history/history_item.h" +#include "history/history_item_helpers.h" #include "history/view/history_view_element.h" // HistoryView::Context. #include "history/view/history_view_context_menu.h" // CopyPostLink. #include "history/view/history_view_schedule_box.h" diff --git a/Telegram/SourceFiles/calls/calls_box_controller.cpp b/Telegram/SourceFiles/calls/calls_box_controller.cpp index e5af11c75..5e26204c4 100644 --- a/Telegram/SourceFiles/calls/calls_box_controller.cpp +++ b/Telegram/SourceFiles/calls/calls_box_controller.cpp @@ -17,6 +17,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "calls/calls_instance.h" #include "history/history.h" #include "history/history_item.h" +#include "history/history_item_helpers.h" #include "mainwidget.h" #include "window/window_session_controller.h" #include "main/main_session.h" diff --git a/Telegram/SourceFiles/calls/group/calls_group_settings.cpp b/Telegram/SourceFiles/calls/group/calls_group_settings.cpp index 5fa269328..210427142 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_settings.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_settings.cpp @@ -24,7 +24,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "lang/lang_keys.h" #include "boxes/share_box.h" #include "history/view/history_view_schedule_box.h" -#include "history/history_message.h" // GetErrorTextForSending. +#include "history/history_item_helpers.h" // GetErrorTextForSending. #include "history/history.h" #include "data/data_histories.h" #include "data/data_session.h" diff --git a/Telegram/SourceFiles/chat_helpers/message_field.cpp b/Telegram/SourceFiles/chat_helpers/message_field.cpp index 3891ab82b..5d5a3b7f3 100644 --- a/Telegram/SourceFiles/chat_helpers/message_field.cpp +++ b/Telegram/SourceFiles/chat_helpers/message_field.cpp @@ -10,7 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/history_widget.h" #include "history/history.h" // History::session #include "history/history_item.h" // HistoryItem::originalText -#include "history/history_message.h" // DropCustomEmoji +#include "history/history_item_helpers.h" // DropCustomEmoji #include "base/qthelp_regex.h" #include "base/qthelp_url.h" #include "base/event_filter.h" diff --git a/Telegram/SourceFiles/data/data_download_manager.cpp b/Telegram/SourceFiles/data/data_download_manager.cpp index e5861c470..c121fa968 100644 --- a/Telegram/SourceFiles/data/data_download_manager.cpp +++ b/Telegram/SourceFiles/data/data_download_manager.cpp @@ -23,7 +23,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "storage/storage_account.h" #include "history/history.h" #include "history/history_item.h" -#include "history/history_message.h" +#include "history/history_item_helpers.h" #include "core/application.h" #include "core/mime_type.h" #include "ui/controls/download_bar.h" diff --git a/Telegram/SourceFiles/data/data_media_types.cpp b/Telegram/SourceFiles/data/data_media_types.cpp index 988e048f7..578f89ea7 100644 --- a/Telegram/SourceFiles/data/data_media_types.cpp +++ b/Telegram/SourceFiles/data/data_media_types.cpp @@ -8,8 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_media_types.h" #include "history/history.h" -#include "history/history_item.h" -#include "history/history_message.h" // CreateMedia. +#include "history/history_item.h" // CreateMedia. #include "history/history_location_manager.h" #include "history/view/history_view_element.h" #include "history/view/history_view_item_preview.h" @@ -235,7 +234,7 @@ template bool UpdateExtendedMedia( Invoice &invoice, - not_null item, + not_null item, const MTPMessageExtendedMedia &media) { return media.match([&](const MTPDmessageExtendedMediaPreview &data) { if (invoice.extendedMedia) { @@ -269,7 +268,7 @@ bool UpdateExtendedMedia( } return changed; }, [&](const MTPDmessageExtendedMedia &data) { - invoice.extendedMedia = HistoryMessage::CreateMedia( + invoice.extendedMedia = HistoryItem::CreateMedia( item, data.vmedia()); return true; @@ -291,7 +290,7 @@ TextForMimeData WithCaptionClipboardText( } Invoice ComputeInvoiceData( - not_null item, + not_null item, const MTPDmessageMediaInvoice &data) { auto description = qs(data.vdescription()); auto result = Invoice{ @@ -1639,7 +1638,7 @@ bool MediaInvoice::updateSentMedia(const MTPMessageMedia &media) { } bool MediaInvoice::updateExtendedMedia( - not_null item, + not_null item, const MTPMessageExtendedMedia &media) { Expects(item == parent()); diff --git a/Telegram/SourceFiles/data/data_media_types.h b/Telegram/SourceFiles/data/data_media_types.h index b9c240c3d..afcc725c3 100644 --- a/Telegram/SourceFiles/data/data_media_types.h +++ b/Telegram/SourceFiles/data/data_media_types.h @@ -12,7 +12,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL class Image; class History; class HistoryItem; -class HistoryMessage; namespace base { template @@ -140,7 +139,7 @@ public: virtual bool updateInlineResultMedia(const MTPMessageMedia &media) = 0; virtual bool updateSentMedia(const MTPMessageMedia &media) = 0; virtual bool updateExtendedMedia( - not_null item, + not_null item, const MTPMessageExtendedMedia &media) { return false; } @@ -428,7 +427,7 @@ public: bool updateInlineResultMedia(const MTPMessageMedia &media) override; bool updateSentMedia(const MTPMessageMedia &media) override; bool updateExtendedMedia( - not_null item, + not_null item, const MTPMessageExtendedMedia &media) override; std::unique_ptr createView( not_null message, @@ -541,7 +540,7 @@ private: TextForMimeData &&caption); [[nodiscard]] Invoice ComputeInvoiceData( - not_null item, + not_null item, const MTPDmessageMediaInvoice &data); [[nodiscard]] Call ComputeCallData(const MTPDmessageActionPhoneCall &call); diff --git a/Telegram/SourceFiles/data/data_replies_list.cpp b/Telegram/SourceFiles/data/data_replies_list.cpp index dedcb57f2..733a0ae05 100644 --- a/Telegram/SourceFiles/data/data_replies_list.cpp +++ b/Telegram/SourceFiles/data/data_replies_list.cpp @@ -9,7 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/history.h" #include "history/history_item.h" -#include "history/history_service.h" +#include "history/history_item_helpers.h" #include "main/main_session.h" #include "data/data_histories.h" #include "data/data_session.h" @@ -30,15 +30,15 @@ constexpr auto kMessagesPerPage = 50; constexpr auto kReadRequestTimeout = 3 * crl::time(1000); constexpr auto kMaxMessagesToDeleteMyTopic = 10; -[[nodiscard]] HistoryService *GenerateDivider( +[[nodiscard]] HistoryItem *GenerateDivider( not_null history, TimeId date, const QString &text) { - return history->makeServiceMessage( + return history->makeMessage( history->nextNonHistoryEntryId(), MessageFlag::FakeHistoryItem, date, - HistoryService::PreparedText{ { .text = text } }); + PreparedServiceText{ { .text = text } }); } [[nodiscard]] bool IsCreating(not_null history, MsgId rootId) { @@ -345,7 +345,7 @@ void RepliesList::injectRootDivider( text()); } else if (_dividerWithComments != withComments) { _dividerWithComments = withComments; - _divider->setServiceText(HistoryService::PreparedText{ { text() } }); + _divider->updateServiceText(PreparedServiceText{ { text() } }); } slice->ids.push_back(_divider->fullId()); } diff --git a/Telegram/SourceFiles/data/data_replies_list.h b/Telegram/SourceFiles/data/data_replies_list.h index e38d9c80a..a33dee56b 100644 --- a/Telegram/SourceFiles/data/data_replies_list.h +++ b/Telegram/SourceFiles/data/data_replies_list.h @@ -11,7 +11,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "base/timer.h" class History; -class HistoryService; namespace Data { @@ -123,7 +122,7 @@ private: rpl::variable> _unreadCount; MsgId _inboxReadTillId = 0; MsgId _outboxReadTillId = 0; - HistoryService *_divider = nullptr; + HistoryItem *_divider = nullptr; bool _dividerWithComments = false; int _beforeId = 0; int _afterId = 0; diff --git a/Telegram/SourceFiles/data/data_scheduled_messages.cpp b/Telegram/SourceFiles/data/data_scheduled_messages.cpp index c6231a695..e33cad8e0 100644 --- a/Telegram/SourceFiles/data/data_scheduled_messages.cpp +++ b/Telegram/SourceFiles/data/data_scheduled_messages.cpp @@ -15,7 +15,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "main/main_session.h" #include "history/history.h" #include "history/history_item_components.h" -#include "history/history_message.h" +#include "history/history_item_helpers.h" #include "apiwrap.h" namespace Data { diff --git a/Telegram/SourceFiles/data/data_session.h b/Telegram/SourceFiles/data/data_session.h index 6e7f5f644..e82b5218b 100644 --- a/Telegram/SourceFiles/data/data_session.h +++ b/Telegram/SourceFiles/data/data_session.h @@ -19,8 +19,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL class Image; class HistoryItem; -class HistoryMessage; -class HistoryService; struct WebPageCollage; enum class WebPageType; enum class NewMessageType; diff --git a/Telegram/SourceFiles/data/data_sponsored_messages.cpp b/Telegram/SourceFiles/data/data_sponsored_messages.cpp index 011ad072d..f6f0a7b4b 100644 --- a/Telegram/SourceFiles/data/data_sponsored_messages.cpp +++ b/Telegram/SourceFiles/data/data_sponsored_messages.cpp @@ -15,7 +15,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_session.h" #include "data/data_user.h" #include "history/history.h" -#include "history/history_message.h" +#include "history/history_item.h" #include "history/view/history_view_element.h" #include "main/main_session.h" #include "ui/image/image_location_factory.h" diff --git a/Telegram/SourceFiles/dialogs/ui/dialogs_layout.cpp b/Telegram/SourceFiles/dialogs/ui/dialogs_layout.cpp index d318efb0b..8bb625bf7 100644 --- a/Telegram/SourceFiles/dialogs/ui/dialogs_layout.cpp +++ b/Telegram/SourceFiles/dialogs/ui/dialogs_layout.cpp @@ -29,8 +29,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/view/history_view_send_action.h" #include "history/view/history_view_item_preview.h" #include "history/history_unread_things.h" -#include "history/history_item_components.h" #include "history/history_item.h" +#include "history/history_item_components.h" +#include "history/history_item_helpers.h" #include "history/history.h" #include "base/unixtime.h" #include "data/data_channel.h" diff --git a/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.cpp b/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.cpp index ea4f2a0bb..2a171099e 100644 --- a/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.cpp +++ b/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.cpp @@ -10,7 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/history.h" #include "history/view/media/history_view_media.h" #include "history/view/media/history_view_web_page.h" -#include "history/history_message.h" +#include "history/history_item.h" #include "history/history_item_components.h" #include "history/history_item_text.h" #include "history/admin_log/history_admin_log_section.h" @@ -582,18 +582,6 @@ HistoryView::Context InnerWidget::elementContext() { return HistoryView::Context::AdminLog; } -std::unique_ptr InnerWidget::elementCreate( - not_null message, - Element *replacing) { - return std::make_unique(this, message, replacing); -} - -std::unique_ptr InnerWidget::elementCreate( - not_null message, - Element *replacing) { - return std::make_unique(this, message, replacing); -} - bool InnerWidget::elementUnderCursor( not_null view) { return (Element::Hovered() == view); @@ -1294,7 +1282,6 @@ void InnerWidget::showContextMenu(QContextMenuEvent *e, bool showFromTouch) { _antiSpamValidator.addAction(_menu, itemId); - auto msg = dynamic_cast(item); if (isUponSelected > 0) { _menu->addAction( tr::lng_context_copy_selected(tr::now), @@ -1311,7 +1298,7 @@ void InnerWidget::showContextMenu(QContextMenuEvent *e, bool showFromTouch) { }), &st::menuIconDownload); } } - if (msg + if (!item->isService() && !link && (view->hasVisibleText() || mediaHasTextForCopy diff --git a/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.h b/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.h index cf92d2ceb..684fba84f 100644 --- a/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.h +++ b/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.h @@ -91,12 +91,6 @@ public: // HistoryView::ElementDelegate interface. HistoryView::Context elementContext() override; - std::unique_ptr elementCreate( - not_null message, - HistoryView::Element *replacing = nullptr) override; - std::unique_ptr elementCreate( - not_null message, - HistoryView::Element *replacing = nullptr) override; bool elementUnderCursor( not_null view) override; [[nodiscard]] float64 elementHighlightOpacity( diff --git a/Telegram/SourceFiles/history/admin_log/history_admin_log_item.cpp b/Telegram/SourceFiles/history/admin_log/history_admin_log_item.cpp index fc7c56071..cdefc990d 100644 --- a/Telegram/SourceFiles/history/admin_log/history_admin_log_item.cpp +++ b/Telegram/SourceFiles/history/admin_log/history_admin_log_item.cpp @@ -9,10 +9,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/admin_log/history_admin_log_inner.h" #include "history/view/history_view_element.h" -#include "history/history_location_manager.h" -#include "history/history_service.h" -#include "history/history_message.h" #include "history/history.h" +#include "history/history_item.h" +#include "history/history_item_helpers.h" +#include "history/history_location_manager.h" #include "api/api_chat_participants.h" #include "api/api_text_entities.h" #include "data/data_channel.h" @@ -773,10 +773,10 @@ void GenerateItems( const TextWithEntities &text, MsgId realId = MsgId(), PhotoData *photo = nullptr) { - auto message = HistoryService::PreparedText{ text }; + auto message = PreparedServiceText{ text }; message.links.push_back(fromLink); addPart( - history->makeServiceMessage( + history->makeMessage( history->nextNonHistoryEntryId(), MessageFlag::AdminLogEntry, date, @@ -1117,10 +1117,10 @@ void GenerateItems( Ui::LayerOption::CloseOther); } }); - auto message = HistoryService::PreparedText { text }; + auto message = PreparedServiceText{ text }; message.links.push_back(fromLink); message.links.push_back(setLink); - addPart(history->makeServiceMessage( + addPart(history->makeMessage( history->nextNonHistoryEntryId(), MessageFlag::AdminLogEntry, date, @@ -1198,10 +1198,10 @@ void GenerateItems( window->showPeerHistory(now); } }); - auto message = HistoryService::PreparedText{ text }; + auto message = PreparedServiceText{ text }; message.links.push_back(fromLink); message.links.push_back(chatLink); - addPart(history->makeServiceMessage( + addPart(history->makeMessage( history->nextNonHistoryEntryId(), MessageFlag::AdminLogEntry, date, @@ -1294,10 +1294,10 @@ void GenerateItems( const auto addServiceMessageWithLink = [&]( const TextWithEntities &text, const ClickHandlerPtr &link) { - auto message = HistoryService::PreparedText{ text }; + auto message = PreparedServiceText{ text }; message.links.push_back(fromLink); message.links.push_back(link); - addPart(history->makeServiceMessage( + addPart(history->makeMessage( history->nextNonHistoryEntryId(), MessageFlag::AdminLogEntry, date, @@ -1363,7 +1363,7 @@ void GenerateItems( const TextWithEntities &text, const MTPExportedChatInvite &data, ClickHandlerPtr additional = nullptr) { - auto message = HistoryService::PreparedText{ text }; + auto message = PreparedServiceText{ text }; message.links.push_back(fromLink); if (!ExtractInviteLink(data).endsWith(Ui::kQEllipsis)) { message.links.push_back(std::make_shared( @@ -1372,7 +1372,7 @@ void GenerateItems( if (additional) { message.links.push_back(std::move(additional)); } - addPart(history->makeServiceMessage( + addPart(history->makeMessage( history->nextNonHistoryEntryId(), MessageFlag::AdminLogEntry, date, diff --git a/Telegram/SourceFiles/history/history.cpp b/Telegram/SourceFiles/history/history.cpp index 6e9d5e621..df1bcbf87 100644 --- a/Telegram/SourceFiles/history/history.cpp +++ b/Telegram/SourceFiles/history/history.cpp @@ -9,9 +9,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/view/history_view_element.h" #include "history/view/history_view_item_preview.h" -#include "history/history_message.h" -#include "history/history_service.h" +#include "history/history_item.h" #include "history/history_item_components.h" +#include "history/history_item_helpers.h" #include "history/history_inner_widget.h" #include "history/history_unread_things.h" #include "dialogs/dialogs_indexed_list.h" @@ -419,7 +419,9 @@ not_null History::createItem( } return result; } - return HistoryItem::Create(this, id, message, localFlags); + return message.match([&](const auto &data) { + return makeMessage(id, data, localFlags); + }); } std::vector> History::createItems( @@ -3080,7 +3082,7 @@ MsgRange History::rangeForDifferenceRequest() const { return MsgRange(); } -HistoryService *History::insertJoinedMessage() { +HistoryItem *History::insertJoinedMessage() { const auto channel = peer->asChannel(); if (!channel || _joinedMessage diff --git a/Telegram/SourceFiles/history/history.h b/Telegram/SourceFiles/history/history.h index e3c408820..3d36ad806 100644 --- a/Telegram/SourceFiles/history/history.h +++ b/Telegram/SourceFiles/history/history.h @@ -19,8 +19,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL class History; class HistoryBlock; class HistoryItem; -class HistoryMessage; -class HistoryService; struct HistoryMessageMarkupData; class HistoryMainElementDelegateMixin; @@ -126,22 +124,14 @@ public: void applyGroupAdminChanges(const base::flat_set &changes); template - not_null makeMessage(Args &&...args) { - return static_cast( + not_null makeMessage(Args &&...args) { + return static_cast( insertItem( - std::make_unique( + std::make_unique( this, std::forward(args)...)).get()); } - template - not_null makeServiceMessage(Args &&...args) { - return static_cast( - insertItem( - std::make_unique( - this, - std::forward(args)...)).get()); - } void destroyMessage(not_null item); void destroyMessagesByDates(TimeId minDate, TimeId maxDate); void destroyMessagesByTopic(MsgId topicRootId); @@ -577,7 +567,7 @@ private: void createLocalDraftFromCloud(MsgId topicRootId); - HistoryService *insertJoinedMessage(); + HistoryItem *insertJoinedMessage(); void insertMessageToBlocks(not_null item); [[nodiscard]] Dialogs::BadgesState computeBadgesState() const; @@ -598,7 +588,7 @@ private: int _height = 0; Element *_unreadBarView = nullptr; Element *_firstUnreadView = nullptr; - HistoryService *_joinedMessage = nullptr; + HistoryItem *_joinedMessage = nullptr; bool _loadedAtTop = false; bool _loadedAtBottom = true; diff --git a/Telegram/SourceFiles/history/history_inner_widget.cpp b/Telegram/SourceFiles/history/history_inner_widget.cpp index cc36aee31..e8ebf4084 100644 --- a/Telegram/SourceFiles/history/history_inner_widget.cpp +++ b/Telegram/SourceFiles/history/history_inner_widget.cpp @@ -11,7 +11,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "core/crash_reports.h" #include "core/click_handler_types.h" #include "history/history.h" -#include "history/history_message.h" +#include "history/history_item.h" +#include "history/history_item_helpers.h" #include "history/view/media/history_view_media.h" #include "history/view/media/history_view_sticker.h" #include "history/view/media/history_view_web_page.h" @@ -145,22 +146,6 @@ public: HistoryView::Context elementContext() override { return HistoryView::Context::History; } - std::unique_ptr elementCreate( - not_null message, - Element *replacing = nullptr) override { - return std::make_unique( - this, - message, - replacing); - } - std::unique_ptr elementCreate( - not_null message, - Element *replacing = nullptr) override { - return std::make_unique( - this, - message, - replacing); - } bool elementUnderCursor( not_null view) override { return (Element::Moused() == view); diff --git a/Telegram/SourceFiles/history/history_item.cpp b/Telegram/SourceFiles/history/history_item.cpp index cb694ccca..a4b917b87 100644 --- a/Telegram/SourceFiles/history/history_item.cpp +++ b/Telegram/SourceFiles/history/history_item.cpp @@ -9,18 +9,21 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "lang/lang_keys.h" #include "mainwidget.h" +#include "calls/calls_instance.h" // Core::App().calls().joinGroupCall. #include "history/view/history_view_element.h" #include "history/view/history_view_item_preview.h" +#include "history/view/history_view_message.h" #include "history/view/history_view_service_message.h" #include "history/view/media/history_view_media_grouped.h" +#include "history/history_item.h" #include "history/history_item_components.h" -#include "history/history_service.h" -#include "history/history_message.h" +#include "history/history_item_helpers.h" #include "history/history_unread_things.h" #include "history/history.h" #include "mtproto/mtproto_config.h" #include "media/clip/media_clip_reader.h" #include "ui/effects/ripple_animation.h" +#include "ui/text/format_values.h" #include "ui/text/text_isolated_emoji.h" #include "ui/text/text_options.h" #include "ui/text/text_utilities.h" @@ -30,6 +33,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "main/main_account.h" #include "main/main_domain.h" #include "main/main_session.h" +#include "main/main_session_settings.h" +#include "menu/menu_ttl_validator.h" #include "apiwrap.h" #include "media/audio/media_audio.h" #include "core/application.h" @@ -37,9 +42,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "window/window_controller.h" #include "window/window_session_controller.h" #include "core/crash_reports.h" +#include "core/click_handler_types.h" #include "base/unixtime.h" +#include "base/timer_rpl.h" #include "api/api_text_entities.h" +#include "api/api_updates.h" #include "dialogs/ui/dialogs_message_view.h" +#include "data/notify/data_notify_settings.h" #include "data/data_scheduled_messages.h" // kScheduledUntilOnlineTimestamp #include "data/data_changes.h" #include "data/data_session.h" @@ -51,194 +60,23 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_forum_topic.h" #include "data/data_channel.h" #include "data/data_chat.h" +#include "data/data_game.h" #include "data/data_user.h" +#include "data/data_group_call.h" // Data::GroupCall::id(). #include "data/data_sponsored_messages.h" +#include "data/data_web_page.h" +#include "chat_helpers/stickers_gift_box_pack.h" +#include "payments/payments_checkout_process.h" // CheckoutProcess::Start. #include "styles/style_dialogs.h" #include "styles/style_chat.h" namespace { constexpr auto kNotificationTextLimit = 255; -constexpr auto kMaxUnreadReactions = 5; // Now 3, but just in case. +constexpr auto kPinnedMessageTextLimit = 16; using ItemPreview = HistoryView::ItemPreview; -enum class MediaCheckResult { - Good, - Unsupported, - Empty, - HasTimeToLive, -}; - -not_null CreateUnsupportedMessage( - not_null history, - MsgId msgId, - MessageFlags flags, - MsgId replyTo, - UserId viaBotId, - TimeId date, - PeerId from) { - const auto siteLink = u"https://desktop.telegram.org"_q; - auto text = TextWithEntities{ - tr::lng_message_unsupported(tr::now, lt_link, siteLink) - }; - TextUtilities::ParseEntities(text, Ui::ItemTextNoMonoOptions().flags); - text.entities.push_front( - EntityInText(EntityType::Italic, 0, text.text.size())); - flags &= ~MessageFlag::HasPostAuthor; - flags |= MessageFlag::Legacy; - const auto groupedId = uint64(); - return history->makeMessage( - msgId, - flags, - replyTo, - viaBotId, - date, - from, - QString(), - text, - MTP_messageMediaEmpty(), - HistoryMessageMarkupData(), - groupedId); -} - -MediaCheckResult CheckMessageMedia(const MTPMessageMedia &media) { - using Result = MediaCheckResult; - return media.match([](const MTPDmessageMediaEmpty &) { - return Result::Good; - }, [](const MTPDmessageMediaContact &) { - return Result::Good; - }, [](const MTPDmessageMediaGeo &data) { - return data.vgeo().match([](const MTPDgeoPoint &) { - return Result::Good; - }, [](const MTPDgeoPointEmpty &) { - return Result::Empty; - }); - }, [](const MTPDmessageMediaVenue &data) { - return data.vgeo().match([](const MTPDgeoPoint &) { - return Result::Good; - }, [](const MTPDgeoPointEmpty &) { - return Result::Empty; - }); - }, [](const MTPDmessageMediaGeoLive &data) { - return data.vgeo().match([](const MTPDgeoPoint &) { - return Result::Good; - }, [](const MTPDgeoPointEmpty &) { - return Result::Empty; - }); - }, [](const MTPDmessageMediaPhoto &data) { - const auto photo = data.vphoto(); - if (data.vttl_seconds()) { - return Result::HasTimeToLive; - } else if (!photo) { - return Result::Empty; - } - return photo->match([](const MTPDphoto &) { - return Result::Good; - }, [](const MTPDphotoEmpty &) { - return Result::Empty; - }); - }, [](const MTPDmessageMediaDocument &data) { - const auto document = data.vdocument(); - if (data.vttl_seconds()) { - return Result::HasTimeToLive; - } else if (!document) { - return Result::Empty; - } - return document->match([](const MTPDdocument &) { - return Result::Good; - }, [](const MTPDdocumentEmpty &) { - return Result::Empty; - }); - }, [](const MTPDmessageMediaWebPage &data) { - return data.vwebpage().match([](const MTPDwebPage &) { - return Result::Good; - }, [](const MTPDwebPageEmpty &) { - return Result::Good; - }, [](const MTPDwebPagePending &) { - return Result::Good; - }, [](const MTPDwebPageNotModified &) { - return Result::Unsupported; - }); - }, [](const MTPDmessageMediaGame &data) { - return data.vgame().match([](const MTPDgame &) { - return Result::Good; - }); - }, [](const MTPDmessageMediaInvoice &) { - return Result::Good; - }, [](const MTPDmessageMediaPoll &) { - return Result::Good; - }, [](const MTPDmessageMediaDice &) { - return Result::Good; - }, [](const MTPDmessageMediaUnsupported &) { - return Result::Unsupported; - }); -} - -[[nodiscard]] MessageFlags FinalizeMessageFlags(MessageFlags flags) { - if (!(flags & MessageFlag::FakeHistoryItem) - && !(flags & MessageFlag::IsOrWasScheduled) - && !(flags & MessageFlag::AdminLogEntry)) { - flags |= MessageFlag::HistoryEntry; - } - return flags; -} - -using OnStackUsers = std::array; -[[nodiscard]] OnStackUsers LookupRecentUnreadReactedUsers( - not_null item) { - auto result = OnStackUsers(); - auto index = 0; - for (const auto &[emoji, reactions] : item->recentReactions()) { - for (const auto &reaction : reactions) { - if (!reaction.unread) { - continue; - } - if (const auto user = reaction.peer->asUser()) { - result[index++] = user; - if (index == result.size()) { - return result; - } - } - } - } - return result; -} - -void CheckReactionNotificationSchedule( - not_null item, - const OnStackUsers &wasUsers) { - // Call to addToUnreadThings may have read the reaction already. - if (!item->hasUnreadReaction()) { - return; - } - for (const auto &[emoji, reactions] : item->recentReactions()) { - for (const auto &reaction : reactions) { - if (!reaction.unread) { - continue; - } - const auto user = reaction.peer->asUser(); - if (!user - || !user->isContact() - || ranges::contains(wasUsers, user)) { - continue; - } - using Status = PeerData::BlockStatus; - if (user->blockStatus() == Status::Unknown) { - user->updateFull(); - } - const auto notification = Data::ItemNotification{ - .item = item, - .reactionSender = user, - .type = Data::ItemNotificationType::Reaction, - }; - item->notificationThread()->pushNotification(notification); - Core::App().notifications().schedule(notification); - return; - } - } -} - } // namespace void HistoryItem::HistoryItem::Destroyer::operator()(HistoryItem *value) { @@ -247,6 +85,523 @@ void HistoryItem::HistoryItem::Destroyer::operator()(HistoryItem *value) { } } +struct HistoryItem::CreateConfig { + PeerId replyToPeer = 0; + MsgId replyTo = 0; + MsgId replyToTop = 0; + bool replyIsTopicPost = false; + UserId viaBotId = 0; + int viewsCount = -1; + QString author; + PeerId senderOriginal = 0; + QString senderNameOriginal; + QString forwardPsaType; + MsgId originalId = 0; + PeerId savedFromPeer = 0; + MsgId savedFromMsgId = 0; + QString authorOriginal; + TimeId originalDate = 0; + TimeId editDate = 0; + HistoryMessageMarkupData markup; + HistoryMessageRepliesData replies; + bool imported = false; + + // For messages created from existing messages (forwarded). + const HistoryMessageReplyMarkup *inlineMarkup = nullptr; +}; + +void HistoryItem::FillForwardedInfo( + CreateConfig &config, + const MTPDmessageFwdHeader &data) { + if (const auto fromId = data.vfrom_id()) { + config.senderOriginal = peerFromMTP(*fromId); + } + config.originalDate = data.vdate().v; + config.senderNameOriginal = qs(data.vfrom_name().value_or_empty()); + config.forwardPsaType = qs(data.vpsa_type().value_or_empty()); + config.originalId = data.vchannel_post().value_or_empty(); + config.authorOriginal = qs(data.vpost_author().value_or_empty()); + const auto savedFromPeer = data.vsaved_from_peer(); + const auto savedFromMsgId = data.vsaved_from_msg_id(); + if (savedFromPeer && savedFromMsgId) { + config.savedFromPeer = peerFromMTP(*savedFromPeer); + config.savedFromMsgId = savedFromMsgId->v; + } + config.imported = data.is_imported(); +} + +std::unique_ptr HistoryItem::CreateMedia( + not_null item, + const MTPMessageMedia &media) { + using Result = std::unique_ptr; + return media.match([&](const MTPDmessageMediaContact &media) -> Result { + return std::make_unique( + item, + media.vuser_id().v, + qs(media.vfirst_name()), + qs(media.vlast_name()), + qs(media.vphone_number())); + }, [&](const MTPDmessageMediaGeo &media) -> Result { + return media.vgeo().match([&](const MTPDgeoPoint &point) -> Result { + return std::make_unique( + item, + Data::LocationPoint(point)); + }, [](const MTPDgeoPointEmpty &) -> Result { + return nullptr; + }); + }, [&](const MTPDmessageMediaGeoLive &media) -> Result { + return media.vgeo().match([&](const MTPDgeoPoint &point) -> Result { + return std::make_unique( + item, + Data::LocationPoint(point)); + }, [](const MTPDgeoPointEmpty &) -> Result { + return nullptr; + }); + }, [&](const MTPDmessageMediaVenue &media) -> Result { + return media.vgeo().match([&](const MTPDgeoPoint &point) -> Result { + return std::make_unique( + item, + Data::LocationPoint(point), + qs(media.vtitle()), + qs(media.vaddress())); + }, [](const MTPDgeoPointEmpty &data) -> Result { + return nullptr; + }); + }, [&](const MTPDmessageMediaPhoto &media) -> Result { + const auto photo = media.vphoto(); + if (media.vttl_seconds()) { + LOG(("App Error: " + "Unexpected MTPMessageMediaPhoto " + "with ttl_seconds in CreateMedia.")); + return nullptr; + } else if (!photo) { + LOG(("API Error: " + "Got MTPMessageMediaPhoto " + "without photo and without ttl_seconds.")); + return nullptr; + } + return photo->match([&](const MTPDphoto &photo) -> Result { + return std::make_unique( + item, + item->history()->owner().processPhoto(photo)); + }, [](const MTPDphotoEmpty &) -> Result { + return nullptr; + }); + }, [&](const MTPDmessageMediaDocument &media) -> Result { + const auto document = media.vdocument(); + if (media.vttl_seconds()) { + LOG(("App Error: " + "Unexpected MTPMessageMediaDocument " + "with ttl_seconds in CreateMedia.")); + return nullptr; + } else if (!document) { + LOG(("API Error: " + "Got MTPMessageMediaDocument " + "without document and without ttl_seconds.")); + return nullptr; + } + return document->match([&](const MTPDdocument &document) -> Result { + return std::make_unique( + item, + item->history()->owner().processDocument(document), + media.is_nopremium()); + }, [](const MTPDdocumentEmpty &) -> Result { + return nullptr; + }); + }, [&](const MTPDmessageMediaWebPage &media) { + return media.vwebpage().match([](const MTPDwebPageEmpty &) -> Result { + return nullptr; + }, [&](const MTPDwebPagePending &webpage) -> Result { + return std::make_unique( + item, + item->history()->owner().processWebpage(webpage)); + }, [&](const MTPDwebPage &webpage) -> Result { + return std::make_unique( + item, + item->history()->owner().processWebpage(webpage)); + }, [](const MTPDwebPageNotModified &) -> Result { + LOG(("API Error: " + "webPageNotModified is unexpected in message media.")); + return nullptr; + }); + }, [&](const MTPDmessageMediaGame &media) -> Result { + return media.vgame().match([&](const MTPDgame &game) { + return std::make_unique( + item, + item->history()->owner().processGame(game)); + }); + }, [&](const MTPDmessageMediaInvoice &media) -> Result { + return std::make_unique( + item, + Data::ComputeInvoiceData(item, media)); + }, [&](const MTPDmessageMediaPoll &media) -> Result { + return std::make_unique( + item, + item->history()->owner().processPoll(media)); + }, [&](const MTPDmessageMediaDice &media) -> Result { + return std::make_unique( + item, + qs(media.vemoticon()), + media.vvalue().v); + }, [](const MTPDmessageMediaEmpty &) -> Result { + return nullptr; + }, [](const MTPDmessageMediaUnsupported &) -> Result { + return nullptr; + }); +} + +HistoryItem::HistoryItem( + not_null history, + MsgId id, + const MTPDmessage &data, + MessageFlags localFlags) +: HistoryItem( + history, + id, + FlagsFromMTP(id, data.vflags().v, localFlags), + data.vdate().v, + data.vfrom_id() ? peerFromMTP(*data.vfrom_id()) : PeerId(0)) { + const auto media = data.vmedia(); + const auto checked = media + ? CheckMessageMedia(*media) + : MediaCheckResult::Good; + if (checked == MediaCheckResult::Unsupported) { + _flags &= ~MessageFlag::HasPostAuthor; + _flags |= MessageFlag::Legacy; + createComponents(data); + setText(UnsupportedMessageText()); + } else if (checked == MediaCheckResult::Empty) { + AddComponents(HistoryServiceData::Bit()); + setServiceText({ + tr::lng_message_empty(tr::now, Ui::Text::WithEntities) + }); + } else if (checked == MediaCheckResult::HasTimeToLive) { + createServiceFromMtp(data); + applyTTL(data); + } else { + createComponents(data); + if (const auto media = data.vmedia()) { + setMedia(*media); + } + auto textWithEntities = TextWithEntities{ + qs(data.vmessage()), + Api::EntitiesFromMTP( + &history->session(), + data.ventities().value_or_empty()) + }; + setText(_media ? textWithEntities : EnsureNonEmpty(textWithEntities)); + if (const auto groupedId = data.vgrouped_id()) { + setGroupId( + MessageGroupId::FromRaw(history->peer->id, groupedId->v)); + } + setReactions(data.vreactions()); + applyTTL(data); + } +} + +HistoryItem::HistoryItem( + not_null history, + MsgId id, + const MTPDmessageService &data, + MessageFlags localFlags) +: HistoryItem( + history, + id, + FlagsFromMTP(id, data.vflags().v, localFlags), + data.vdate().v, + data.vfrom_id() ? peerFromMTP(*data.vfrom_id()) : PeerId(0)) { + if (data.vaction().type() != mtpc_messageActionPhoneCall) { + createServiceFromMtp(data); + } else { + createComponents(CreateConfig()); + _media = std::make_unique( + this, + Data::ComputeCallData(data.vaction().c_messageActionPhoneCall())); + setTextValue({}); + } + applyTTL(data); +} + +HistoryItem::HistoryItem( + not_null history, + MsgId id, + const MTPDmessageEmpty &data, + MessageFlags localFlags) +: HistoryItem( + history, + id, + localFlags, + TimeId(0), + PreparedServiceText{ tr::lng_message_empty( + tr::now, + Ui::Text::WithEntities) }) { +} + +HistoryItem::HistoryItem( + not_null history, + MsgId id, + MessageFlags flags, + TimeId date, + PreparedServiceText &&message, + PeerId from, + PhotoData *photo) +: HistoryItem(history, id, flags, date, from) { + setServiceText(std::move(message)); + if (photo) { + _media = std::make_unique( + this, + history->peer, + photo); + } +} + +HistoryItem::HistoryItem( + not_null history, + MsgId id, + MessageFlags flags, + TimeId date, + PeerId from, + const QString &postAuthor, + not_null original, + MsgId topicRootId) +: HistoryItem( + history, + id, + (NewForwardedFlags(history->peer, from, original) | flags), + date, + from) { + const auto peer = history->peer; + + auto config = CreateConfig(); + + const auto originalMedia = original->media(); + const auto dropForwardInfo = original->computeDropForwardedInfo(); + config.replyTo = config.replyToTop = topicRootId; + config.replyIsTopicPost = (topicRootId != 0); + if (!dropForwardInfo) { + config.originalDate = original->dateOriginal(); + if (const auto info = original->hiddenSenderInfo()) { + config.senderNameOriginal = info->name; + } else if (const auto senderOriginal = original->senderOriginal()) { + config.senderOriginal = senderOriginal->id; + if (senderOriginal->isChannel()) { + config.originalId = original->idOriginal(); + } + } else { + Unexpected("Corrupt forwarded information in message."); + } + config.authorOriginal = original->authorOriginal(); + } + if (peer->isSelf()) { + // + // iOS app sends you to the original post if we forward a forward from channel. + // But server returns not the original post but the forward in saved_from_... + // + //if (config.originalId) { + // config.savedFromPeer = config.senderOriginal; + // config.savedFromMsgId = config.originalId; + //} else { + config.savedFromPeer = original->history()->peer->id; + config.savedFromMsgId = original->id; + //} + } + if (flags & MessageFlag::HasPostAuthor) { + config.author = postAuthor; + } + if (const auto fwdViaBot = original->viaBot()) { + config.viaBotId = peerToUser(fwdViaBot->id); + } else if (originalMedia && originalMedia->game()) { + if (const auto sender = original->senderOriginal()) { + if (const auto user = sender->asUser()) { + if (user->isBot()) { + config.viaBotId = peerToUser(user->id); + } + } + } + } + const auto fwdViewsCount = original->viewsCount(); + if (fwdViewsCount > 0) { + config.viewsCount = fwdViewsCount; + } else if ((isPost() && !isScheduled()) + || (original->senderOriginal() + && original->senderOriginal()->isChannel())) { + config.viewsCount = 1; + } + + const auto mediaOriginal = original->media(); + if (CopyMarkupToForward(original)) { + config.inlineMarkup = original->inlineReplyMarkup(); + } + createComponents(std::move(config)); + + const auto ignoreMedia = [&] { + if (mediaOriginal && mediaOriginal->webpage()) { + if (peer->amRestricted(ChatRestriction::EmbedLinks)) { + return true; + } + } + return false; + }; + if (mediaOriginal && !ignoreMedia()) { + _media = mediaOriginal->clone(this); + } + + const auto dropCustomEmoji = dropForwardInfo + && !history->session().premium() + && !history->peer->isSelf(); + setText(dropCustomEmoji + ? DropCustomEmoji(original->originalText()) + : original->originalText()); +} + +HistoryItem::HistoryItem( + not_null history, + MsgId id, + MessageFlags flags, + MsgId replyTo, + UserId viaBotId, + TimeId date, + PeerId from, + const QString &postAuthor, + const TextWithEntities &textWithEntities, + const MTPMessageMedia &media, + HistoryMessageMarkupData &&markup, + uint64 groupedId) +: HistoryItem( + history, + id, + flags, + date, + (flags & MessageFlag::HasFromId) ? from : 0) { + createComponentsHelper( + flags, + replyTo, + viaBotId, + postAuthor, + std::move(markup)); + setMedia(media); + setText(textWithEntities); + if (groupedId) { + setGroupId(MessageGroupId::FromRaw(history->peer->id, groupedId)); + } +} + +HistoryItem::HistoryItem( + not_null history, + MsgId id, + MessageFlags flags, + MsgId replyTo, + UserId viaBotId, + TimeId date, + PeerId from, + const QString &postAuthor, + not_null document, + const TextWithEntities &caption, + HistoryMessageMarkupData &&markup) +: HistoryItem( + history, + id, + flags, + date, + (flags & MessageFlag::HasFromId) ? from : 0) { + createComponentsHelper( + flags, + replyTo, + viaBotId, + postAuthor, + std::move(markup)); + + const auto skipPremiumEffect = !history->session().premium(); + _media = std::make_unique( + this, + document, + skipPremiumEffect); + setText(caption); +} + +HistoryItem::HistoryItem( + not_null history, + MsgId id, + MessageFlags flags, + MsgId replyTo, + UserId viaBotId, + TimeId date, + PeerId from, + const QString &postAuthor, + not_null photo, + const TextWithEntities &caption, + HistoryMessageMarkupData &&markup) +: HistoryItem( + history, + id, + flags, + date, + (flags & MessageFlag::HasFromId) ? from : 0) { + createComponentsHelper( + flags, + replyTo, + viaBotId, + postAuthor, + std::move(markup)); + + _media = std::make_unique(this, photo); + setText(caption); +} + +HistoryItem::HistoryItem( + not_null history, + MsgId id, + MessageFlags flags, + MsgId replyTo, + UserId viaBotId, + TimeId date, + PeerId from, + const QString &postAuthor, + not_null game, + HistoryMessageMarkupData &&markup) +: HistoryItem( + history, + id, + flags, + date, + (flags & MessageFlag::HasFromId) ? from : 0) { + createComponentsHelper( + flags, + replyTo, + viaBotId, + postAuthor, + std::move(markup)); + + _media = std::make_unique(this, game); + setTextValue({}); +} + +HistoryItem::HistoryItem( + not_null history, + MsgId id, + Data::SponsoredFrom from, + const TextWithEntities &textWithEntities, + HistoryItem *injectedAfter) +: HistoryItem( + history, + id, + ((history->peer->isChannel() ? MessageFlag::Post : MessageFlag(0)) + //| (from.peer ? MessageFlag::HasFromId : MessageFlag(0)) + | MessageFlag::Local), + HistoryItem::NewMessageDate(injectedAfter + ? injectedAfter->date() + : 0), + /*from.peer ? from.peer->id : */PeerId(0)) { + createComponentsHelper( + _flags, + MsgId(0), // replyTo + UserId(0), // viaBotId + QString(), // postAuthor + HistoryMessageMarkupData()); + setText(textWithEntities); + setSponsoredFrom(from); +} + HistoryItem::HistoryItem( not_null history, MsgId id, @@ -263,6 +618,16 @@ HistoryItem::HistoryItem( } } +HistoryItem::~HistoryItem() { + _media = nullptr; + clearSavedMedia(); + if (auto reply = Get()) { + reply->clearData(this); + } + clearDependencyMessage(); + applyTTL(0); +} + TimeId HistoryItem::date() const { return _date; } @@ -271,12 +636,136 @@ TimeId HistoryItem::NewMessageDate(TimeId scheduled) { return scheduled ? scheduled : base::unixtime::now(); } -void HistoryItem::applyServiceDateEdition(const MTPDmessageService &data) { - const auto date = data.vdate().v; - if (_date == date) { - return; +HistoryServiceDependentData *HistoryItem::GetServiceDependentData() { + if (const auto pinned = Get()) { + return pinned; + } else if (const auto gamescore = Get()) { + return gamescore; + } else if (const auto payment = Get()) { + return payment; + } else if (const auto info = Get()) { + return info; } - _date = date; + return nullptr; +} + +auto HistoryItem::GetServiceDependentData() const +-> const HistoryServiceDependentData * { + return const_cast(this)->GetServiceDependentData(); +} + +void HistoryItem::dependencyItemRemoved(not_null dependency) { + if (const auto reply = Get()) { + const auto documentId = reply->replyToDocumentId; + reply->itemRemoved(this, dependency); + if (documentId != reply->replyToDocumentId + && generateLocalEntitiesByReply()) { + _history->owner().requestItemTextRefresh(this); + } + } else { + clearDependencyMessage(); + updateDependentServiceText(); + } +} + +void HistoryItem::updateDependencyItem() { + if (const auto reply = Get()) { + const auto documentId = reply->replyToDocumentId; + const auto webpageId = reply->replyToWebPageId; + reply->updateData(this, true); + const auto mediaIdChanged = (documentId != reply->replyToDocumentId) + || (webpageId != reply->replyToWebPageId); + if (mediaIdChanged && generateLocalEntitiesByReply()) { + _history->owner().requestItemTextRefresh(this); + } + } else if (GetServiceDependentData()) { + updateServiceDependent(true); + } +} + +void HistoryItem::updateDependentServiceText() { + if (Has()) { + updateServiceText(preparePinnedText()); + } else if (Has()) { + updateServiceText(prepareGameScoreText()); + } else if (Has()) { + updateServiceText(preparePaymentSentText()); + } +} + +bool HistoryItem::updateServiceDependent(bool force) { + auto dependent = GetServiceDependentData(); + Assert(dependent != nullptr); + + if (!force) { + if (!dependent->msgId || dependent->msg) { + return true; + } + } + + if (!dependent->lnk) { + dependent->lnk = JumpToMessageClickHandler( + (dependent->peerId + ? _history->owner().peer(dependent->peerId) + : _history->peer), + dependent->msgId, + fullId()); + } + auto gotDependencyItem = false; + if (!dependent->msg) { + dependent->msg = _history->owner().message( + (dependent->peerId + ? dependent->peerId + : _history->peer->id), + dependent->msgId); + if (dependent->msg) { + if (dependent->msg->isEmpty()) { + // Really it is deleted. + dependent->msg = nullptr; + force = true; + } else { + _history->owner().registerDependentMessage( + this, + dependent->msg); + gotDependencyItem = true; + } + } + } + if (dependent->msg) { + updateDependentServiceText(); + } else if (force) { + if (dependent->msgId > 0) { + dependent->msgId = 0; + gotDependencyItem = true; + } + updateDependentServiceText(); + } + if (force && gotDependencyItem) { + Core::App().notifications().checkDelayed(); + } + return (dependent->msg || !dependent->msgId); +} + +MsgId HistoryItem::dependencyMsgId() const { + if (auto dependent = GetServiceDependentData()) { + return dependent->msgId; + } + return replyToId(); +} + +void HistoryItem::checkBuyButton() { + if (const auto invoice = _media ? _media->invoice() : nullptr) { + if (invoice->receiptMsgId) { + replaceBuyWithReceiptInMarkup(); + } + } +} + +bool HistoryItem::notificationReady() const { + if (const auto dependent = GetServiceDependentData()) { + return (dependent->msg || !dependent->msgId); + } + return true; } void HistoryItem::finishEdition(int oldKeyboardTop) { @@ -307,6 +796,176 @@ void HistoryItem::setGroupId(MessageGroupId groupId) { _history->owner().groups().registerMessage(this); } +bool HistoryItem::checkCommentsLinkedChat(ChannelId id) const { + if (!id) { + return true; + } else if (const auto channel = _history->peer->asChannel()) { + if (channel->linkedChatKnown() + || !(channel->flags() & ChannelDataFlag::HasLink)) { + const auto linked = channel->linkedChat(); + if (!linked || peerToChannel(linked->id) != id) { + return false; + } + } + return true; + } + return false; +} + +void HistoryItem::setReplyMarkup(HistoryMessageMarkupData &&markup) { + const auto requestUpdate = [&] { + history()->owner().requestItemResize(this); + history()->session().changes().messageUpdated( + this, + Data::MessageUpdate::Flag::ReplyMarkup); + }; + if (markup.isNull()) { + if (_flags & MessageFlag::HasReplyMarkup) { + _flags &= ~MessageFlag::HasReplyMarkup; + if (Has()) { + RemoveComponents(HistoryMessageReplyMarkup::Bit()); + } + requestUpdate(); + } + return; + } + + // optimization: don't create markup component for the case + // MTPDreplyKeyboardHide with flags = 0, assume it has f_zero flag + if (markup.isTrivial()) { + bool changed = false; + if (Has()) { + RemoveComponents(HistoryMessageReplyMarkup::Bit()); + changed = true; + } + if (!(_flags & MessageFlag::HasReplyMarkup)) { + _flags |= MessageFlag::HasReplyMarkup; + changed = true; + } + if (changed) { + requestUpdate(); + } + } else { + if (!(_flags & MessageFlag::HasReplyMarkup)) { + _flags |= MessageFlag::HasReplyMarkup; + } + if (!Has()) { + AddComponents(HistoryMessageReplyMarkup::Bit()); + } + Get()->updateData(std::move(markup)); + requestUpdate(); + } +} + +void HistoryItem::setCommentsInboxReadTill(MsgId readTillId) { + const auto views = Get(); + if (!views) { + return; + } + const auto newReadTillId = std::max(readTillId.bare, int64(1)); + const auto ignore = (newReadTillId < views->commentsInboxReadTillId); + if (ignore) { + return; + } + const auto changed = (newReadTillId > views->commentsInboxReadTillId); + if (!changed) { + return; + } + const auto wasUnread = areCommentsUnread(); + views->commentsInboxReadTillId = newReadTillId; + if (wasUnread && !areCommentsUnread()) { + _history->owner().requestItemRepaint(this); + } +} + +void HistoryItem::setCommentsMaxId(MsgId maxId) { + if (const auto views = Get()) { + if (views->commentsMaxId != maxId) { + const auto wasUnread = areCommentsUnread(); + views->commentsMaxId = maxId; + if (wasUnread != areCommentsUnread()) { + _history->owner().requestItemRepaint(this); + } + } + } +} + +void HistoryItem::setCommentsPossibleMaxId(MsgId possibleMaxId) { + if (const auto views = Get()) { + if (views->commentsMaxId < possibleMaxId) { + const auto wasUnread = areCommentsUnread(); + views->commentsMaxId = possibleMaxId; + if (!wasUnread && areCommentsUnread()) { + _history->owner().requestItemRepaint(this); + } + } + } +} + +bool HistoryItem::areCommentsUnread() const { + const auto views = Get(); + if (!views + || !views->commentsMegagroupId + || !checkCommentsLinkedChat(views->commentsMegagroupId)) { + return false; + } + const auto till = views->commentsInboxReadTillId; + if (views->commentsInboxReadTillId < 2 || views->commentsMaxId <= till) { + return false; + } + const auto group = views->commentsMegagroupId + ? _history->owner().historyLoaded( + peerFromChannel(views->commentsMegagroupId)) + : _history.get(); + return !group || (views->commentsMaxId > group->inboxReadTillId()); +} + +FullMsgId HistoryItem::commentsItemId() const { + if (const auto views = Get()) { + return FullMsgId( + PeerId(views->commentsMegagroupId), + views->commentsRootId); + } + return FullMsgId(); +} + +void HistoryItem::setCommentsItemId(FullMsgId id) { + if (id.peer == _history->peer->id) { + if (id.msg != this->id) { + if (const auto reply = Get()) { + reply->replyToMsgTop = id.msg; + } + } + } else if (const auto views = Get()) { + if (const auto channelId = peerToChannel(id.peer)) { + if (views->commentsMegagroupId != channelId) { + views->commentsMegagroupId = channelId; + _history->owner().requestItemResize(this); + } + views->commentsRootId = id.msg; + } + } +} + +void HistoryItem::setServiceText(PreparedServiceText &&prepared) { + AddComponents(HistoryServiceData::Bit()); + _flags &= ~MessageFlag::HasTextLinks; + const auto data = Get(); + const auto had = !_text.empty(); + _text = std::move(prepared.text); + data->textLinks = std::move(prepared.links); + if (had) { + _history->owner().requestItemTextRefresh(this); + } +} + +void HistoryItem::updateServiceText(PreparedServiceText &&text) { + setServiceText(std::move(text)); + _history->owner().requestItemResize(this); + invalidateChatListEntry(); + _history->owner().updateDependentMessages(this); +} + HistoryMessageReplyMarkup *HistoryItem::inlineReplyMarkup() { if (const auto markup = Get()) { if (markup->data.flags & ReplyMarkupFlag::Inline) { @@ -324,7 +983,7 @@ ReplyKeyboard *HistoryItem::inlineReplyKeyboard() { } ChannelData *HistoryItem::discussionPostOriginalSender() const { - if (!history()->peer->isMegagroup()) { + if (!_history->peer->isMegagroup()) { return nullptr; } if (const auto forwarded = Get()) { @@ -341,7 +1000,7 @@ bool HistoryItem::isDiscussionPost() const { } HistoryItem *HistoryItem::lookupDiscussionPostOriginal() const { - if (!history()->peer->isMegagroup()) { + if (!_history->peer->isMegagroup()) { return nullptr; } const auto forwarded = Get(); @@ -363,18 +1022,30 @@ PeerData *HistoryItem::displayFrom() const { return nullptr; } } else if (const auto forwarded = Get()) { - if (history()->peer->isSelf() || history()->peer->isRepliesChat() || forwarded->imported) { + if (_history->peer->isSelf() || _history->peer->isRepliesChat() || forwarded->imported) { return forwarded->originalSender; } } return author().get(); } +std::unique_ptr HistoryItem::createView( + not_null delegate, + HistoryView::Element *replacing) { + if (isService()) { + return std::make_unique( + delegate, + this, + replacing); + } + return std::make_unique(delegate, this, replacing); +} + void HistoryItem::invalidateChatListEntry() { - history()->session().changes().messageUpdated( + _history->session().changes().messageUpdated( this, Data::MessageUpdate::Flag::DialogRowRefresh); - history()->lastItemDialogsView().itemInvalidated(this); + _history->lastItemDialogsView().itemInvalidated(this); if (const auto topic = this->topic()) { topic->lastItemDialogsView().itemInvalidated(this); } @@ -383,7 +1054,7 @@ void HistoryItem::invalidateChatListEntry() { void HistoryItem::customEmojiRepaint() { if (!(_flags & MessageFlag::CustomEmojiRepainting)) { _flags |= MessageFlag::CustomEmojiRepainting; - history()->owner().requestItemRepaint(this); + _history->owner().requestItemRepaint(this); } } @@ -447,6 +1118,13 @@ void HistoryItem::markMediaAndMentionRead() { topic->unreadMentions().erase(id); } } + + if (const auto selfdestruct = Get()) { + if (!selfdestruct->destructAt) { + selfdestruct->destructAt = crl::now() + selfdestruct->timeToLive; + _history->owner().selfDestructIn(this, selfdestruct->timeToLive); + } + } } void HistoryItem::markReactionsRead() { @@ -465,7 +1143,7 @@ void HistoryItem::markReactionsRead() { bool HistoryItem::markContentsRead(bool fromThisClient) { if (hasUnreadReaction()) { if (fromThisClient) { - history()->owner().requestUnreadReactionsAnimation(this); + _history->owner().requestUnreadReactionsAnimation(this); } markReactionsRead(); return true; @@ -480,17 +1158,17 @@ void HistoryItem::setIsPinned(bool pinned) { const auto changed = (isPinned() != pinned); if (pinned) { _flags |= MessageFlag::Pinned; - auto &storage = history()->session().storage(); + auto &storage = _history->session().storage(); storage.add(Storage::SharedMediaAddExisting( - history()->peer->id, + _history->peer->id, MsgId(0), // topicRootId Storage::SharedMediaType::Pinned, id, { id, id })); - history()->setHasPinnedMessages(true); + _history->setHasPinnedMessages(true); if (const auto topic = this->topic()) { storage.add(Storage::SharedMediaAddExisting( - history()->peer->id, + _history->peer->id, topic->rootId(), Storage::SharedMediaType::Pinned, id, @@ -499,17 +1177,30 @@ void HistoryItem::setIsPinned(bool pinned) { } } else { _flags &= ~MessageFlag::Pinned; - history()->session().storage().remove(Storage::SharedMediaRemoveOne( - history()->peer->id, + _history->session().storage().remove(Storage::SharedMediaRemoveOne( + _history->peer->id, Storage::SharedMediaType::Pinned, id)); } if (changed) { - history()->owner().notifyItemDataChange(this); + _history->owner().notifyItemDataChange(this); } } void HistoryItem::returnSavedMedia() { + if (!isEditingMedia()) { + return; + } + const auto wasGrouped = history()->owner().groups().isGrouped(this); + _media = std::move(_savedLocalEditMediaData->media); + setText(_savedLocalEditMediaData->text); + clearSavedMedia(); + if (wasGrouped) { + history()->owner().groups().refreshMessage(this, true); + } else { + history()->owner().requestItemViewRefresh(this); + history()->owner().updateDependentMessages(this); + } } void HistoryItem::savePreviousMedia() { @@ -587,7 +1278,7 @@ UserData *HistoryItem::getMessageBot() const { } auto bot = from()->asUser(); if (!bot) { - bot = history()->peer->asUser(); + bot = _history->peer->asUser(); } return (bot && bot->isBot()) ? bot : nullptr; } @@ -666,28 +1357,98 @@ void HistoryItem::clearMainView() { _mainView = nullptr; } -void HistoryItem::addToUnreadThings(HistoryUnreadThings::AddType type) { +void HistoryItem::applyEdition(HistoryMessageEdition &&edition) { + int keyboardTop = -1; + //if (!pendingResize()) {// #TODO edit bot message + // if (auto keyboard = inlineReplyKeyboard()) { + // int h = st::msgBotKbButton.margin + keyboard->naturalHeight(); + // keyboardTop = _height - h + st::msgBotKbButton.margin - marginBottom(); + // } + //} + + if (edition.isEditHide) { + _flags |= MessageFlag::HideEdited; + } else { + _flags &= ~MessageFlag::HideEdited; + } + + if (edition.editDate != -1) { + //_flags |= MTPDmessage::Flag::f_edit_date; + if (!Has()) { + AddComponents(HistoryMessageEdited::Bit()); + } + auto edited = Get(); + edited->date = edition.editDate; + } + + if (!edition.useSameMarkup) { + setReplyMarkup(base::take(edition.replyMarkup)); + } + if (!isLocalUpdateMedia()) { + refreshMedia(edition.mtpMedia); + } + if (!edition.useSameReactions) { + updateReactions(edition.mtpReactions); + } + changeViewsCount(edition.views); + setForwardsCount(edition.forwards); + setText(_media + ? edition.textWithEntities + : EnsureNonEmpty(edition.textWithEntities)); + if (!edition.useSameReplies) { + if (!edition.replies.isNull) { + if (checkRepliesPts(edition.replies)) { + setReplies(base::take(edition.replies)); + } + } else { + clearReplies(); + } + } + + applyTTL(edition.ttl); + + finishEdition(keyboardTop); } -void HistoryItem::applyEditionToHistoryCleared() { - applyEdition( - MTP_messageService( - MTP_flags(0), - MTP_int(id), - peerToMTP(PeerId(0)), // from_id - peerToMTP(history()->peer->id), - MTPMessageReplyHeader(), - MTP_int(date()), - MTP_messageActionHistoryClear(), - MTPint() // ttl_period - ).c_messageService()); +void HistoryItem::applyEdition(const MTPDmessageService &message) { + if (message.vaction().type() == mtpc_messageActionHistoryClear) { + const auto wasGrouped = history()->owner().groups().isGrouped(this); + setReplyMarkup({}); + refreshMedia(nullptr); + setTextValue({}); + changeViewsCount(-1); + setForwardsCount(-1); + if (wasGrouped) { + history()->owner().groups().unregisterMessage(this); + } + clearDependencyMessage(); + UpdateComponents(0); + createServiceFromMtp(message); + applyServiceDateEdition(message); + finishEditionToEmpty(); + } else if (isService()) { + clearDependencyMessage(); + UpdateComponents(0); + createServiceFromMtp(message); + applyServiceDateEdition(message); + finishEdition(-1); + } +} + +void HistoryItem::applyEdition(const MTPMessageExtendedMedia &media) { + if (const auto existing = this->media()) { + if (existing->updateExtendedMedia(this, media)) { + checkBuyButton(); + finishEdition(-1); + } + } } void HistoryItem::applySentMessage(const MTPDmessage &data) { updateSentContent({ qs(data.vmessage()), Api::EntitiesFromMTP( - &history()->session(), + &_history->session(), data.ventities().value_or_empty()) }, data.vmedia()); updateReplyMarkup(HistoryMessageMarkupData(data.vreply_markup())); @@ -713,9 +1474,14 @@ void HistoryItem::applySentMessage(const MTPDmessage &data) { contributeToSlowmode(data.vdate().v); indexAsNewItem(); invalidateChatListEntry(); - history()->owner().notifyItemDataChange(this); - history()->owner().requestItemTextRefresh(this); - history()->owner().updateDependentMessages(this); + if (const auto period = data.vttl_period(); period && period->v > 0) { + applyTTL(data.vdate().v + period->v); + } else { + applyTTL(0); + } + _history->owner().notifyItemDataChange(this); + _history->owner().requestItemTextRefresh(this); + _history->owner().updateDependentMessages(this); } void HistoryItem::applySentMessage( @@ -725,7 +1491,7 @@ void HistoryItem::applySentMessage( updateSentContent({ text, Api::EntitiesFromMTP( - &history()->session(), + &_history->session(), data.ventities().value_or_empty()) }, data.vmedia()); contributeToSlowmode(data.vdate().v); @@ -733,6 +1499,154 @@ void HistoryItem::applySentMessage( indexAsNewItem(); } invalidateChatListEntry(); + if (const auto period = data.vttl_period(); period && period->v > 0) { + applyTTL(data.vdate().v + period->v); + } else { + applyTTL(0); + } +} + +void HistoryItem::updateSentContent( + const TextWithEntities &textWithEntities, + const MTPMessageMedia *media) { + setText(textWithEntities); + if (_flags & MessageFlag::FromInlineBot) { + if (!media || !_media || !_media->updateInlineResultMedia(*media)) { + refreshSentMedia(media); + } + _flags &= ~MessageFlag::FromInlineBot; + } else if (media || _media) { + if (!media || !_media || !_media->updateSentMedia(*media)) { + refreshSentMedia(media); + } + } + history()->owner().requestItemResize(this); +} + +void HistoryItem::updateForwardedInfo(const MTPMessageFwdHeader *fwd) { + const auto forwarded = Get(); + if (!fwd) { + if (forwarded) { + LOG(("API Error: Server removed forwarded information.")); + } + return; + } else if (!forwarded) { + LOG(("API Error: Server added forwarded information.")); + return; + } + fwd->match([&](const MTPDmessageFwdHeader &data) { + auto config = CreateConfig(); + FillForwardedInfo(config, data); + setupForwardedComponent(config); + history()->owner().requestItemResize(this); + }); +} + +void HistoryItem::applyEditionToHistoryCleared() { + applyEdition( + MTP_messageService( + MTP_flags(0), + MTP_int(id), + peerToMTP(PeerId(0)), // from_id + peerToMTP(_history->peer->id), + MTPMessageReplyHeader(), + MTP_int(date()), + MTP_messageActionHistoryClear(), + MTPint() // ttl_period + ).c_messageService()); +} + +void HistoryItem::updateReplyMarkup(HistoryMessageMarkupData &&markup) { + setReplyMarkup(std::move(markup)); +} + +void HistoryItem::contributeToSlowmode(TimeId realDate) { + if (const auto channel = history()->peer->asChannel()) { + if (out() && isRegular() && !isService()) { + channel->growSlowmodeLastMessage(realDate ? realDate : date()); + } + } +} + +void HistoryItem::addToUnreadThings(HistoryUnreadThings::AddType type) { + if (!isRegular()) { + return; + } + const auto mention = isUnreadMention(); + const auto reaction = hasUnreadReaction(); + if (!mention && !reaction) { + return; + } + const auto topic = this->topic(); + const auto history = this->history(); + const auto changes = &history->session().changes(); + if (mention) { + if (history->unreadMentions().add(id, type)) { + changes->historyUpdated( + history, + Data::HistoryUpdate::Flag::UnreadMentions); + } + if (topic && topic->unreadMentions().add(id, type)) { + changes->topicUpdated( + topic, + Data::TopicUpdate::Flag::UnreadMentions); + } + } + if (reaction) { + const auto toHistory = history->unreadReactions().add(id, type); + const auto toTopic = topic && topic->unreadReactions().add(id, type); + if (toHistory || toTopic) { + if (type == HistoryUnreadThings::AddType::New) { + changes->messageUpdated( + this, + Data::MessageUpdate::Flag::NewUnreadReaction); + } + if (hasUnreadReaction()) { + if (toHistory) { + changes->historyUpdated( + history, + Data::HistoryUpdate::Flag::UnreadReactions); + } + if (toTopic) { + changes->topicUpdated( + topic, + Data::TopicUpdate::Flag::UnreadReactions); + } + } + } + } +} + +void HistoryItem::destroyHistoryEntry() { + if (isUnreadMention()) { + history()->unreadMentions().erase(id); + if (const auto topic = this->topic()) { + topic->unreadMentions().erase(id); + } + } + if (hasUnreadReaction()) { + history()->unreadReactions().erase(id); + if (const auto topic = this->topic()) { + topic->unreadReactions().erase(id); + } + } + if (const auto reply = Get()) { + changeReplyToTopCounter(reply, -1); + } +} + +Storage::SharedMediaTypesMask HistoryItem::sharedMediaTypes() const { + auto result = Storage::SharedMediaTypesMask {}; + if (const auto media = this->media()) { + result.set(media->sharedMediaTypes()); + } + if (hasTextLinks()) { + result.set(Storage::SharedMediaType::Link); + } + if (isPinned()) { + result.set(Storage::SharedMediaType::Pinned); + } + return result; } void HistoryItem::indexAsNewItem() { @@ -754,6 +1668,49 @@ void HistoryItem::indexAsNewItem() { } } +void HistoryItem::incrementReplyToTopCounter() { + if (const auto reply = Get()) { + changeReplyToTopCounter(reply, 1); + } +} + +void HistoryItem::changeReplyToTopCounter( + not_null reply, + int delta) { + if (!isRegular() || !_history->peer->isMegagroup()) { + return; + } else if (delta > 0) { + _history->session().changes().messageUpdated( + this, + Data::MessageUpdate::Flag::ReplyToTopAdded); + } + const auto topId = reply->replyToTop(); + if (!topId) { + return; + } + const auto top = _history->owner().message(_history->peer->id, topId); + if (!top) { + return; + } + const auto from = displayFrom(); + const auto replier = from ? from->id : PeerId(); + top->changeRepliesCount(delta, replier); + if (const auto original = top->lookupDiscussionPostOriginal()) { + original->changeRepliesCount(delta, replier); + } +} + +QString HistoryItem::notificationHeader() const { + if (isService()) { + return QString(); + } else if (out() && isFromScheduled() && !_history->peer->isSelf()) { + return tr::lng_from_you(tr::now); + } else if (!_history->peer->isUser() && !isPost()) { + return from()->name(); + } + return QString(); +} + void HistoryItem::setRealId(MsgId newId) { Expects(_flags & MessageFlag::BeingSent); Expects(IsClientMsgId(id)); @@ -775,7 +1732,15 @@ void HistoryItem::setRealId(MsgId newId) { } _history->owner().notifyItemDataChange(this); + _history->owner().groups().refreshMessage(this); _history->owner().requestItemResize(this); + + if (const auto reply = Get()) { + if (reply->replyToLink()) { + reply->setReplyToLinkFrom(this); + } + changeReplyToTopCounter(reply, 1); + } } bool HistoryItem::canPin() const { @@ -788,15 +1753,34 @@ bool HistoryItem::canPin() const { } bool HistoryItem::allowsSendNow() const { - return false; + return !isService() + && isScheduled() + && !isSending() + && !hasFailed() + && !isEditingMedia(); } bool HistoryItem::allowsForward() const { - return false; + return !isService() + && isRegular() + && !forbidsForward() + && history()->peer->allowsForwarding() + && (!_media || _media->allowsForward()); +} + +bool HistoryItem::isTooOldForEdit(TimeId now) const { + return !_history->peer->canEditMessagesIndefinitely() + && !isScheduled() + && (now - date() >= _history->session().serverConfig().editTimeLimit); } bool HistoryItem::allowsEdit(TimeId now) const { - return false; + return !isService() + && canBeEdited() + && !isTooOldForEdit(now) + && (!_media || _media->allowsEdit()) + && !isLegacyMessage() + && !isEditingMedia(); } bool HistoryItem::canBeEdited() const { @@ -872,8 +1856,8 @@ bool HistoryItem::canDelete() const { } bool HistoryItem::canDeleteForEveryone(TimeId now) const { - const auto peer = history()->peer; - const auto &config = history()->session().serverConfig(); + const auto peer = _history->peer; + const auto &config = _history->session().serverConfig(); const auto messageToMyself = peer->isSelf(); const auto messageTooOld = messageToMyself ? false @@ -914,16 +1898,16 @@ bool HistoryItem::canDeleteForEveryone(TimeId now) const { bool HistoryItem::suggestReport() const { if (out() || isService() || !isRegular()) { return false; - } else if (const auto channel = history()->peer->asChannel()) { + } else if (const auto channel = _history->peer->asChannel()) { return true; - } else if (const auto user = history()->peer->asUser()) { + } else if (const auto user = _history->peer->asUser()) { return user->isBot(); } return false; } bool HistoryItem::suggestBanReport() const { - const auto channel = history()->peer->asChannel(); + const auto channel = _history->peer->asChannel(); if (!channel || !channel->canRestrictParticipant(from())) { return false; } @@ -931,7 +1915,7 @@ bool HistoryItem::suggestBanReport() const { } bool HistoryItem::suggestDeleteAllReport() const { - auto channel = history()->peer->asChannel(); + auto channel = _history->peer->asChannel(); if (!channel || !channel->canDeleteMessages()) { return false; } @@ -955,7 +1939,7 @@ void HistoryItem::toggleReaction( if (!_reactions) { _reactions = std::make_unique(this); const auto canViewReactions = !isDiscussionPost() - && (history()->peer->isChat() || history()->peer->isMegagroup()); + && (_history->peer->isChat() || _history->peer->isMegagroup()); if (canViewReactions) { _flags |= MessageFlag::CanViewReactions; } @@ -965,72 +1949,12 @@ void HistoryItem::toggleReaction( if (_reactions->empty()) { _reactions = nullptr; _flags &= ~MessageFlag::CanViewReactions; - history()->owner().notifyItemDataChange(this); + _history->owner().notifyItemDataChange(this); } } else { _reactions->add(reaction, (source == ReactionSource::Selector)); } - history()->owner().notifyItemDataChange(this); -} - -void HistoryItem::setReactions(const MTPMessageReactions *reactions) { - Expects(!_reactions); - - if (changeReactions(reactions) && _reactions->hasUnread()) { - _flags |= MessageFlag::HasUnreadReaction; - } -} - -void HistoryItem::updateReactions(const MTPMessageReactions *reactions) { - const auto wasRecentUsers = LookupRecentUnreadReactedUsers(this); - const auto hadUnread = hasUnreadReaction(); - const auto changed = changeReactions(reactions); - if (!changed) { - return; - } - const auto hasUnread = _reactions && _reactions->hasUnread(); - if (hasUnread && !hadUnread) { - _flags |= MessageFlag::HasUnreadReaction; - - addToUnreadThings(HistoryUnreadThings::AddType::New); - } else if (!hasUnread && hadUnread) { - markReactionsRead(); - } - CheckReactionNotificationSchedule(this, wasRecentUsers); - history()->owner().notifyItemDataChange(this); -} - -bool HistoryItem::changeReactions(const MTPMessageReactions *reactions) { - if (reactions || _reactionsLastRefreshed) { - _reactionsLastRefreshed = crl::now(); - } - if (!reactions) { - _flags &= ~MessageFlag::CanViewReactions; - return (base::take(_reactions) != nullptr); - } - return reactions->match([&](const MTPDmessageReactions &data) { - if (data.is_can_see_list()) { - _flags |= MessageFlag::CanViewReactions; - } else { - _flags &= ~MessageFlag::CanViewReactions; - } - if (data.vresults().v.isEmpty()) { - return (base::take(_reactions) != nullptr); - } else if (!_reactions) { - _reactions = std::make_unique(this); - } - const auto min = data.is_min(); - const auto &list = data.vresults().v; - const auto &recent = data.vrecent_reactions().value_or_empty(); - if (min && hasUnreadReaction()) { - // We can't update reactions from min if we have unread. - if (_reactions->checkIfChanged(list, recent)) { - updateReactionsUnknown(); - } - return false; - } - return _reactions->change(list, recent, min); - }); + _history->owner().notifyItemDataChange(this); } void HistoryItem::updateReactionsUnknown() { @@ -1105,7 +2029,7 @@ Data::MessagePosition HistoryItem::position() const { bool HistoryItem::computeDropForwardedInfo() const { const auto media = this->media(); return (media && media->dropForwardedInfo()) - || (history()->peer->isSelf() + || (_history->peer->isSelf() && !Has() && (!media || !media->forceForwardedInfo())); } @@ -1116,7 +2040,7 @@ bool HistoryItem::inThread(MsgId rootId) const { } not_null HistoryItem::author() const { - return (isPost() && !isSponsored()) ? history()->peer : from(); + return (isPost() && !isSponsored()) ? _history->peer : from(); } TimeId HistoryItem::dateOriginal() const { @@ -1130,7 +2054,7 @@ PeerData *HistoryItem::senderOriginal() const { if (const auto forwarded = Get()) { return forwarded->originalSender; } - const auto peer = history()->peer; + const auto peer = _history->peer; return (peer->isChannel() && !peer->isMegagroup()) ? peer : from(); } @@ -1172,6 +2096,220 @@ MsgId HistoryItem::idOriginal() const { return id; } +TextWithEntities HistoryItem::originalText() const { + return isService() ? TextWithEntities() : _text; +} + +TextWithEntities HistoryItem::originalTextWithLocalEntities() const { + return isService() + ? TextWithEntities() + : withLocalEntities(originalText()); +} + +TextForMimeData HistoryItem::clipboardText() const { + return isService() + ? TextForMimeData() + : TextForMimeData::WithExpandedLinks(_text); +} + +bool HistoryItem::changeViewsCount(int count) { + const auto views = Get(); + if (!views + || views->views.count == count + || (count >= 0 && views->views.count > count)) { + return false; + } + + views->views.count = count; + return true; +} + +void HistoryItem::setForwardsCount(int count) { + const auto views = Get(); + if (!views + || views->forwardsCount == count + || (count >= 0 && views->forwardsCount > count)) { + return; + } + + views->forwardsCount = count; + history()->owner().notifyItemDataChange(this); +} + +void HistoryItem::setPostAuthor(const QString &author) { + auto msgsigned = Get(); + if (author.isEmpty()) { + if (!msgsigned) { + return; + } + RemoveComponents(HistoryMessageSigned::Bit()); + history()->owner().requestItemResize(this); + return; + } + if (!msgsigned) { + AddComponents(HistoryMessageSigned::Bit()); + msgsigned = Get(); + } else if (msgsigned->author == author) { + return; + } + msgsigned->author = author; + msgsigned->isAnonymousRank = !isDiscussionPost() + && this->author()->isMegagroup(); + history()->owner().requestItemResize(this); +} + +void HistoryItem::setReplies(HistoryMessageRepliesData &&data) { + if (data.isNull) { + return; + } + auto views = Get(); + if (!views) { + AddComponents(HistoryMessageViews::Bit()); + views = Get(); + } + const auto &repliers = data.recentRepliers; + const auto count = data.repliesCount; + const auto channelId = data.channelId; + const auto readTillId = data.readMaxId + ? std::max({ + views->commentsInboxReadTillId.bare, + data.readMaxId.bare, + int64(1), + }) + : views->commentsInboxReadTillId; + const auto maxId = data.maxId ? data.maxId : views->commentsMaxId; + const auto countsChanged = (views->replies.count != count) + || (views->commentsInboxReadTillId != readTillId) + || (views->commentsMaxId != maxId); + const auto megagroupChanged = (views->commentsMegagroupId != channelId); + const auto recentChanged = (views->recentRepliers != repliers); + if (!countsChanged && !megagroupChanged && !recentChanged) { + return; + } + views->replies.count = count; + if (recentChanged) { + views->recentRepliers = repliers; + } + const auto wasUnread = areCommentsUnread(); + views->commentsMegagroupId = channelId; + views->commentsInboxReadTillId = readTillId; + views->commentsMaxId = maxId; + if (wasUnread != areCommentsUnread()) { + history()->owner().requestItemRepaint(this); + } + refreshRepliesText(views, megagroupChanged); +} + +void HistoryItem::clearReplies() { + auto views = Get(); + if (!views) { + return; + } + const auto viewsPart = views->views; + if (viewsPart.count < 0) { + RemoveComponents(HistoryMessageViews::Bit()); + } else { + *views = HistoryMessageViews(); + views->views = viewsPart; + } + history()->owner().requestItemResize(this); +} + +void HistoryItem::refreshRepliesText( + not_null views, + bool forceResize) { + if (views->commentsMegagroupId) { + views->replies.text = (views->replies.count > 0) + ? tr::lng_comments_open_count( + tr::now, + lt_count_short, + views->replies.count) + : tr::lng_comments_open_none(tr::now); + views->replies.textWidth = st::semiboldFont->width( + views->replies.text); + views->repliesSmall.text = (views->replies.count > 0) + ? Lang::FormatCountToShort(views->replies.count).string + : QString(); + const auto hadText = (views->repliesSmall.textWidth > 0); + views->repliesSmall.textWidth = (views->replies.count > 0) + ? st::semiboldFont->width(views->repliesSmall.text) + : 0; + const auto hasText = (views->repliesSmall.textWidth > 0); + if (hasText != hadText) { + forceResize = true; + } + } + if (forceResize) { + history()->owner().requestItemResize(this); + } else { + history()->owner().requestItemRepaint(this); + } +} + +void HistoryItem::changeRepliesCount(int delta, PeerId replier) { + const auto views = Get(); + const auto limit = HistoryMessageViews::kMaxRecentRepliers; + if (!views) { + return; + } + + // Update full count. + if (views->replies.count < 0) { + return; + } + views->replies.count = std::max(views->replies.count + delta, 0); + if (replier && views->commentsMegagroupId) { + if (delta < 0) { + views->recentRepliers.erase( + ranges::remove(views->recentRepliers, replier), + end(views->recentRepliers)); + } else if (!ranges::contains(views->recentRepliers, replier)) { + views->recentRepliers.insert(views->recentRepliers.begin(), replier); + while (views->recentRepliers.size() > limit) { + views->recentRepliers.pop_back(); + } + } + } + refreshRepliesText(views); + history()->owner().notifyItemDataChange(this); +} + +void HistoryItem::setReplyFields( + MsgId replyTo, + MsgId replyToTop, + bool isForumPost) { + if (isScheduled()) { + return; + } else if (const auto data = GetServiceDependentData()) { + if ((data->topId != replyToTop) && !IsServerMsgId(data->topId)) { + data->topId = replyToTop; + if (isForumPost) { + data->topicPost = true; + } + } + } else if (const auto reply = Get()) { + reply->topicPost = isForumPost; + if ((reply->replyToMsgId != replyTo) + && !IsServerMsgId(reply->replyToMsgId)) { + reply->replyToMsgId = replyTo; + if (!reply->updateData(this)) { + RequestDependentMessageData( + this, + reply->replyToPeerId, + reply->replyToMsgId); + } + } + if ((reply->replyToMsgTop != replyToTop) + && !IsServerMsgId(reply->replyToMsgTop)) { + reply->replyToMsgTop = replyToTop; + changeReplyToTopCounter(reply, 1); + } + } + if (const auto topic = this->topic()) { + topic->maybeSetLastMessage(this); + } +} + void HistoryItem::updateDate(TimeId newDate) { if (canUpdateDate() && _date != newDate) { _date = newDate; @@ -1183,38 +2321,41 @@ bool HistoryItem::canUpdateDate() const { return isScheduled(); } -void HistoryItem::applyTTL(const MTPDmessage &data) { - if (const auto period = data.vttl_period()) { - if (period->v > 0) { - applyTTL(data.vdate().v + period->v); - } - } -} - -void HistoryItem::applyTTL(const MTPDmessageService &data) { - if (const auto period = data.vttl_period()) { - if (period->v > 0) { - applyTTL(data.vdate().v + period->v); - } - } -} - void HistoryItem::applyTTL(TimeId destroyAt) { const auto previousDestroyAt = std::exchange(_ttlDestroyAt, destroyAt); if (previousDestroyAt) { - history()->owner().unregisterMessageTTL(previousDestroyAt, this); + _history->owner().unregisterMessageTTL(previousDestroyAt, this); } if (!_ttlDestroyAt) { return; } else if (base::unixtime::now() >= _ttlDestroyAt) { - const auto session = &history()->session(); + const auto session = &_history->session(); crl::on_main(session, [session, id = fullId()]{ if (const auto item = session->data().message(id)) { item->destroy(); } }); } else { - history()->owner().registerMessageTTL(_ttlDestroyAt, this); + _history->owner().registerMessageTTL(_ttlDestroyAt, this); + } +} + +void HistoryItem::replaceBuyWithReceiptInMarkup() { + if (const auto markup = inlineReplyMarkup()) { + for (auto &row : markup->data.rows) { + for (auto &button : row) { + if (button.type == HistoryMessageMarkupButton::Type::Buy) { + const auto receipt = tr::lng_payments_receipt_button(tr::now); + if (button.text != receipt) { + button.text = receipt; + if (markup->inlineKeyboard) { + markup->inlineKeyboard = nullptr; + _history->owner().requestItemResize(this); + } + } + } + } + } } } @@ -1226,6 +2367,40 @@ bool HistoryItem::isRegular() const { return isHistoryEntry() && !isLocal(); } +int HistoryItem::viewsCount() const { + if (const auto views = Get()) { + return std::max(views->views.count, 0); + } + return hasViews() ? 1 : -1; +} + +int HistoryItem::repliesCount() const { + if (const auto views = Get()) { + if (!checkCommentsLinkedChat(views->commentsMegagroupId)) { + return 0; + } + return std::max(views->replies.count, 0); + } + return 0; +} + +bool HistoryItem::repliesAreComments() const { + if (const auto views = Get()) { + return (views->commentsMegagroupId != 0) + && checkCommentsLinkedChat(views->commentsMegagroupId); + } + return false; +} + +bool HistoryItem::externalReply() const { + if (!_history->peer->isRepliesChat()) { + return false; + } else if (const auto forwarded = Get()) { + return forwarded->savedFromPeer && forwarded->savedFromMsgId; + } + return false; +} + bool HistoryItem::hasExtendedMediaPreview() const { if (const auto media = _media.get()) { if (const auto invoice = media->invoice()) { @@ -1241,23 +2416,28 @@ void HistoryItem::sendFailed() { _flags = (_flags | MessageFlag::SendingFailed) & ~MessageFlag::BeingSent; _history->owner().notifyItemDataChange(this); - history()->session().changes().historyUpdated( - history(), + _history->session().changes().historyUpdated( + _history, Data::HistoryUpdate::Flag::ClientSideMessages); } bool HistoryItem::needCheck() const { - return (out() && !isEmpty()) || (!isRegular() && history()->peer->isSelf()); + return (out() && !isEmpty()) + || (!isRegular() && _history->peer->isSelf()); +} + +bool HistoryItem::isService() const { + return Has(); } bool HistoryItem::unread(not_null thread) const { // Messages from myself are always read, unless scheduled. - if (history()->peer->isSelf() && !isFromScheduled()) { + if (_history->peer->isSelf() && !isFromScheduled()) { return false; } // All messages in converted chats are always read. - if (history()->peer->migrateTo()) { + if (_history->peer->migrateTo()) { return false; } @@ -1266,11 +2446,11 @@ bool HistoryItem::unread(not_null thread) const { return false; } if (out()) { - if (const auto user = history()->peer->asUser()) { + if (const auto user = _history->peer->asUser()) { if (user->isBot() && !user->isSupport()) { return false; } - } else if (const auto channel = history()->peer->asChannel()) { + } else if (const auto channel = _history->peer->asChannel()) { if (!channel->isMegagroup()) { return false; } @@ -1282,6 +2462,60 @@ bool HistoryItem::unread(not_null thread) const { return out() || (_flags & MessageFlag::ClientSideUnread); } +MsgId HistoryItem::replyToId() const { + if (const auto reply = Get()) { + return reply->replyToId(); + } + return 0; +} + +MsgId HistoryItem::replyToTop() const { + if (const auto reply = Get()) { + return reply->replyToTop(); + } else if (const auto data = GetServiceDependentData()) { + return data->topId; + } + return 0; +} + +MsgId HistoryItem::topicRootId() const { + if (const auto reply = Get() + ; reply && reply->topicPost) { + return reply->replyToTop(); + } else if (const auto data = GetServiceDependentData() + ; data && data->topicPost && data->topId) { + return data->topId; + } else if (const auto info = Get()) { + if (info->created()) { + return id; + } + } + return Data::ForumTopic::kGeneralId; +} + +void HistoryItem::setText(const TextWithEntities &textWithEntities) { + for (const auto &entity : textWithEntities.entities) { + auto type = entity.type(); + if (type == EntityType::Url + || type == EntityType::CustomUrl + || type == EntityType::Email) { + _flags |= MessageFlag::HasTextLinks; + break; + } + } + setTextValue((_media && _media->consumeMessageText(textWithEntities)) + ? TextWithEntities() + : std::move(textWithEntities)); +} + +void HistoryItem::setTextValue(TextWithEntities text) { + const auto had = !_text.empty(); + _text = std::move(text); + if (had) { + history()->owner().requestItemTextRefresh(this); + } +} + bool HistoryItem::showNotification() const { const auto channel = _history->peer->asChannel(); if (channel && !channel->amIn()) { @@ -1322,12 +2556,20 @@ TextWithEntities HistoryItem::notificationText() const { Ui::kQEllipsis); } -const std::vector &HistoryItem::customTextLinks() const { - static const auto result = std::vector(); - return result; -} - ItemPreview HistoryItem::toPreview(ToPreviewOptions options) const { + if (isService()) { + // Don't show small media for service messages (chat photo changed). + // Because larger version is shown exactly to the left of the small. + //auto media = _media ? _media->toPreview(options) : ItemPreview(); + return { + .text = Ui::Text::Wrapped( + notificationText(), + EntityType::PlainLink), + //.images = std::move(media.images), + //.loadingContext = std::move(media.loadingContext), + }; + } + auto result = [&]() -> ItemPreview { if (_media) { return _media->toPreview(options); @@ -1375,178 +2617,1819 @@ ItemPreview HistoryItem::toPreview(ToPreviewOptions options) const { } TextWithEntities HistoryItem::inReplyText() const { - return toPreview({ - .hideSender = true, - .generateImages = false, - }).text; -} - -HistoryItem::~HistoryItem() { - applyTTL(0); -} - -Main::Session *SessionByUniqueId(uint64 sessionUniqueId) { - if (!sessionUniqueId) { - return nullptr; + if (!isService()) { + return toPreview({ + .hideSender = true, + .generateImages = false, + }).text; } - for (const auto &[index, account] : Core::App().domain().accounts()) { - if (const auto session = account->maybeSession()) { - if (session->uniqueId() == sessionUniqueId) { - return session; + auto result = notificationText(); + const auto &name = author()->name(); + TextUtilities::Trim(result); + if (result.text.startsWith(name)) { + result = Ui::Text::Mid(result, name.size()); + TextUtilities::Trim(result); + } + return Ui::Text::Wrapped(result, EntityType::PlainLink); +} + +const std::vector &HistoryItem::customTextLinks() const { + static const auto kEmpty = std::vector(); + const auto service = Get(); + return service ? service->textLinks : kEmpty; +} + +void HistoryItem::createComponents(CreateConfig &&config) { + uint64 mask = 0; + if (config.replyTo) { + mask |= HistoryMessageReply::Bit(); + } + if (config.viaBotId) { + mask |= HistoryMessageVia::Bit(); + } + if (config.viewsCount >= 0 || !config.replies.isNull) { + mask |= HistoryMessageViews::Bit(); + } + if (!config.author.isEmpty()) { + mask |= HistoryMessageSigned::Bit(); + } else if (_history->peer->isMegagroup() // Discussion posts signatures. + && config.savedFromPeer + && !config.authorOriginal.isEmpty()) { + const auto savedFrom = _history->owner().peerLoaded( + config.savedFromPeer); + if (savedFrom && savedFrom->isChannel()) { + mask |= HistoryMessageSigned::Bit(); + } + } else if ((_history->peer->isSelf() || _history->peer->isRepliesChat()) + && !config.authorOriginal.isEmpty()) { + mask |= HistoryMessageSigned::Bit(); + } + if (config.editDate != TimeId(0)) { + mask |= HistoryMessageEdited::Bit(); + } + if (config.originalDate != 0) { + mask |= HistoryMessageForwarded::Bit(); + } + if (!config.markup.isTrivial()) { + mask |= HistoryMessageReplyMarkup::Bit(); + } else if (config.inlineMarkup) { + mask |= HistoryMessageReplyMarkup::Bit(); + } + + UpdateComponents(mask); + + if (const auto reply = Get()) { + reply->replyToPeerId = config.replyToPeer; + reply->replyToMsgId = config.replyTo; + reply->replyToMsgTop = isScheduled() ? 0 : config.replyToTop; + reply->topicPost = config.replyIsTopicPost; + if (!reply->updateData(this)) { + RequestDependentMessageData( + this, + reply->replyToPeerId, + reply->replyToMsgId); + } + } + if (const auto via = Get()) { + via->create(&_history->owner(), config.viaBotId); + } + if (const auto views = Get()) { + changeViewsCount(config.viewsCount); + if (config.replies.isNull + && isSending() + && config.markup.isNull()) { + if (const auto broadcast = _history->peer->asBroadcast()) { + if (const auto linked = broadcast->linkedChat()) { + config.replies.isNull = false; + config.replies.channelId = peerToChannel(linked->id); + } + } + } + setReplies(std::move(config.replies)); + } + if (const auto edited = Get()) { + edited->date = config.editDate; + } + if (const auto msgsigned = Get()) { + msgsigned->author = config.author.isEmpty() + ? config.authorOriginal + : config.author; + msgsigned->isAnonymousRank = !isDiscussionPost() + && author()->isMegagroup(); + } + setupForwardedComponent(config); + if (const auto markup = Get()) { + if (!config.markup.isTrivial()) { + markup->updateData(std::move(config.markup)); + } else if (config.inlineMarkup) { + markup->createForwarded(*config.inlineMarkup); + } + if (markup->data.flags & ReplyMarkupFlag::HasSwitchInlineButton) { + _flags |= MessageFlag::HasSwitchInlineButton; + } + } else if (!config.markup.isNull()) { + _flags |= MessageFlag::HasReplyMarkup; + } else { + _flags &= ~MessageFlag::HasReplyMarkup; + } +} + +bool HistoryItem::checkRepliesPts( + const HistoryMessageRepliesData &data) const { + const auto channel = _history->peer->asChannel(); + const auto pts = channel + ? channel->pts() + : _history->session().updates().pts(); + return (data.pts >= pts); +} + +void HistoryItem::setupForwardedComponent(const CreateConfig &config) { + const auto forwarded = Get(); + if (!forwarded) { + return; + } + forwarded->originalDate = config.originalDate; + const auto originalSender = config.senderOriginal + ? config.senderOriginal + : !config.senderNameOriginal.isEmpty() + ? PeerId() + : from()->id; + forwarded->originalSender = originalSender + ? _history->owner().peer(originalSender).get() + : nullptr; + if (!forwarded->originalSender) { + forwarded->hiddenSenderInfo = std::make_unique( + config.senderNameOriginal, + config.imported); + } + forwarded->originalId = config.originalId; + forwarded->originalAuthor = config.authorOriginal; + forwarded->psaType = config.forwardPsaType; + forwarded->savedFromPeer = _history->owner().peerLoaded( + config.savedFromPeer); + forwarded->savedFromMsgId = config.savedFromMsgId; + forwarded->imported = config.imported; +} + +bool HistoryItem::generateLocalEntitiesByReply() const { + using namespace HistoryView; + if (!_media) { + return true; + } else if (const auto document = _media->document()) { + return !DurationForTimestampLinks(document); + } else if (const auto webpage = _media->webpage()) { + return (webpage->type != WebPageType::Video) + && !DurationForTimestampLinks(webpage); + } + return true; +} + +TextWithEntities HistoryItem::withLocalEntities( + const TextWithEntities &textWithEntities) const { + using namespace HistoryView; + if (!generateLocalEntitiesByReply()) { + if (!_media) { + } else if (const auto document = _media->document()) { + if (const auto duration = DurationForTimestampLinks(document)) { + return AddTimestampLinks( + textWithEntities, + duration, + TimestampLinkBase(document, fullId())); + } + } else if (const auto webpage = _media->webpage()) { + if (const auto duration = DurationForTimestampLinks(webpage)) { + return AddTimestampLinks( + textWithEntities, + duration, + TimestampLinkBase(webpage, fullId())); + } + } + return textWithEntities; + } + if (const auto reply = Get()) { + const auto document = reply->replyToDocumentId + ? _history->owner().document(reply->replyToDocumentId).get() + : nullptr; + const auto webpage = reply->replyToWebPageId + ? _history->owner().webpage(reply->replyToWebPageId).get() + : nullptr; + if (document) { + if (const auto duration = DurationForTimestampLinks(document)) { + const auto context = reply->replyToMsg->fullId(); + return AddTimestampLinks( + textWithEntities, + duration, + TimestampLinkBase(document, context)); + } + } else if (webpage) { + if (const auto duration = DurationForTimestampLinks(webpage)) { + const auto context = reply->replyToMsg->fullId(); + return AddTimestampLinks( + textWithEntities, + duration, + TimestampLinkBase(webpage, context)); } } } - return nullptr; + return textWithEntities; } -HistoryItem *MessageByGlobalId(GlobalMsgId globalId) { - const auto sessionId = globalId.itemId ? globalId.sessionUniqueId : 0; - if (const auto session = SessionByUniqueId(sessionId)) { - return session->data().message(globalId.itemId); +void HistoryItem::setSponsoredFrom(const Data::SponsoredFrom &from) { + AddComponents(HistoryMessageSponsored::Bit()); + const auto sponsored = Get(); + sponsored->sender = std::make_unique( + from.title, + false); + sponsored->recommended = from.isRecommended; + sponsored->isForceUserpicDisplay = from.isForceUserpicDisplay; + if (from.userpic.location.valid()) { + sponsored->sender->customUserpic.set( + &_history->session(), + from.userpic); } - return nullptr; + + using Type = HistoryMessageSponsored::Type; + sponsored->type = from.isExactPost + ? Type::Post + : from.isBot + ? Type::Bot + : from.isBroadcast + ? Type::Broadcast + : (from.peer && from.peer->isUser()) + ? Type::User + : Type::Group; } -QDateTime ItemDateTime(not_null item) { - return base::unixtime::parse(item->date()); +void HistoryItem::createComponentsHelper( + MessageFlags flags, + MsgId replyTo, + UserId viaBotId, + const QString &postAuthor, + HistoryMessageMarkupData &&markup) { + auto config = CreateConfig(); + config.viaBotId = viaBotId; + if (flags & MessageFlag::HasReplyInfo) { + config.replyTo = replyTo; + const auto to = LookupReplyTo(_history, replyTo); + const auto replyToTop = LookupReplyToTop(to); + config.replyToTop = replyToTop ? replyToTop : replyTo; + const auto forum = _history->asForum(); + config.replyIsTopicPost = LookupReplyIsTopicPost(to) + || (to && to->Has()) + || (forum && forum->creating(config.replyToTop)); + } + config.markup = std::move(markup); + if (flags & MessageFlag::HasPostAuthor) config.author = postAuthor; + if (flags & MessageFlag::HasViews) config.viewsCount = 1; + + createComponents(std::move(config)); } -QString ItemDateText(not_null item, bool isUntilOnline) { - const auto dateText = langDayOfMonthFull(ItemDateTime(item).date()); - return !item->isScheduled() - ? dateText - : isUntilOnline - ? tr::lng_scheduled_date_until_online(tr::now) - : tr::lng_scheduled_date(tr::now, lt_date, dateText); +void HistoryItem::setReactions(const MTPMessageReactions *reactions) { + Expects(!_reactions); + + if (changeReactions(reactions) && _reactions->hasUnread()) { + _flags |= MessageFlag::HasUnreadReaction; + } } -bool IsItemScheduledUntilOnline(not_null item) { - return item->isScheduled() - && (item->date() == - Data::ScheduledMessages::kScheduledUntilOnlineTimestamp); +void HistoryItem::updateReactions(const MTPMessageReactions *reactions) { + const auto wasRecentUsers = LookupRecentUnreadReactedUsers(this); + const auto hadUnread = hasUnreadReaction(); + const auto changed = changeReactions(reactions); + if (!changed) { + return; + } + const auto hasUnread = _reactions && _reactions->hasUnread(); + if (hasUnread && !hadUnread) { + _flags |= MessageFlag::HasUnreadReaction; + + addToUnreadThings(HistoryUnreadThings::AddType::New); + } else if (!hasUnread && hadUnread) { + markReactionsRead(); + } + CheckReactionNotificationSchedule(this, wasRecentUsers); + _history->owner().notifyItemDataChange(this); } -ClickHandlerPtr goToMessageClickHandler( - not_null item, - FullMsgId returnToId) { - return goToMessageClickHandler( - item->history()->peer, - item->id, - returnToId); +bool HistoryItem::changeReactions(const MTPMessageReactions *reactions) { + if (reactions || _reactionsLastRefreshed) { + _reactionsLastRefreshed = crl::now(); + } + if (!reactions) { + _flags &= ~MessageFlag::CanViewReactions; + return (base::take(_reactions) != nullptr); + } + return reactions->match([&](const MTPDmessageReactions &data) { + if (data.is_can_see_list()) { + _flags |= MessageFlag::CanViewReactions; + } else { + _flags &= ~MessageFlag::CanViewReactions; + } + if (data.vresults().v.isEmpty()) { + return (base::take(_reactions) != nullptr); + } else if (!_reactions) { + _reactions = std::make_unique(this); + } + const auto min = data.is_min(); + const auto &list = data.vresults().v; + const auto &recent = data.vrecent_reactions().value_or_empty(); + if (min && hasUnreadReaction()) { + // We can't update reactions from min if we have unread. + if (_reactions->checkIfChanged(list, recent)) { + updateReactionsUnknown(); + } + return false; + } + return _reactions->change(list, recent, min); + }); } -ClickHandlerPtr goToMessageClickHandler( - not_null peer, - MsgId msgId, - FullMsgId returnToId) { - return std::make_shared([=] { - const auto separate = Core::App().separateWindowForPeer(peer); - const auto controller = separate - ? separate->sessionController() - : peer->session().tryResolveWindow(); - if (controller) { - auto params = Window::SectionShow{ - Window::SectionShow::Way::Forward - }; - params.origin = Window::SectionShow::OriginMessage{ - returnToId - }; - if (const auto item = peer->owner().message(peer, msgId)) { - controller->showMessage(item, params); +void HistoryItem::applyTTL(const MTPDmessage &data) { + if (const auto period = data.vttl_period()) { + if (period->v > 0) { + applyTTL(data.vdate().v + period->v); + } + } +} + +void HistoryItem::applyTTL(const MTPDmessageService &data) { + if (const auto period = data.vttl_period()) { + if (period->v > 0) { + applyTTL(data.vdate().v + period->v); + } + } +} + +void HistoryItem::createComponents(const MTPDmessage &data) { + auto config = CreateConfig(); + if (const auto forwarded = data.vfwd_from()) { + forwarded->match([&](const MTPDmessageFwdHeader &data) { + FillForwardedInfo(config, data); + }); + } + if (const auto reply = data.vreply_to()) { + reply->match([&](const MTPDmessageReplyHeader &data) { + if (const auto peer = data.vreply_to_peer_id()) { + config.replyToPeer = peerFromMTP(*peer); + if (config.replyToPeer == _history->peer->id) { + config.replyToPeer = 0; + } + } + const auto id = data.vreply_to_msg_id().v; + config.replyTo = data.is_reply_to_scheduled() + ? _history->owner().scheduledMessages().localMessageId(id) + : id; + config.replyToTop = data.vreply_to_top_id().value_or(id); + config.replyIsTopicPost = data.is_forum_topic(); + }); + } + config.viaBotId = data.vvia_bot_id().value_or_empty(); + config.viewsCount = data.vviews().value_or(-1); + config.replies = isScheduled() + ? HistoryMessageRepliesData() + : HistoryMessageRepliesData(data.vreplies()); + config.markup = HistoryMessageMarkupData(data.vreply_markup()); + config.editDate = data.vedit_date().value_or_empty(); + config.author = qs(data.vpost_author().value_or_empty()); + createComponents(std::move(config)); +} + +void HistoryItem::refreshMedia(const MTPMessageMedia *media) { + const auto was = (_media != nullptr); + if (const auto invoice = was ? _media->invoice() : nullptr) { + if (invoice->extendedMedia) { + return; + } + } + _media = nullptr; + if (media) { + setMedia(*media); + } + if (was || _media) { + if (const auto views = Get()) { + refreshRepliesText(views); + } + } +} + +void HistoryItem::refreshSentMedia(const MTPMessageMedia *media) { + const auto wasGrouped = history()->owner().groups().isGrouped(this); + refreshMedia(media); + if (wasGrouped) { + history()->owner().groups().refreshMessage(this); + } else { + history()->owner().requestItemViewRefresh(this); + } +} + +void HistoryItem::createServiceFromMtp(const MTPDmessage &message) { + AddComponents(HistoryServiceData::Bit()); + + const auto media = message.vmedia(); + Assert(media != nullptr); + + const auto mediaType = media->type(); + switch (mediaType) { + case mtpc_messageMediaPhoto: { + if (message.is_media_unread()) { + const auto &photo = media->c_messageMediaPhoto(); + const auto ttl = photo.vttl_seconds(); + Assert(ttl != nullptr); + + setSelfDestruct(HistoryServiceSelfDestruct::Type::Photo, ttl->v); + if (out()) { + setServiceText({ + tr::lng_ttl_photo_sent(tr::now, Ui::Text::WithEntities) + }); } else { - controller->showPeerHistory(peer, params, msgId); + auto result = PreparedServiceText(); + result.links.push_back(fromLink()); + result.text = tr::lng_ttl_photo_received( + tr::now, + lt_from, + fromLinkText(), // Link 1. + Ui::Text::WithEntities); + setServiceText(std::move(result)); + } + } else { + setServiceText({ + tr::lng_ttl_photo_expired(tr::now, Ui::Text::WithEntities) + }); + } + } break; + case mtpc_messageMediaDocument: { + if (message.is_media_unread()) { + const auto &document = media->c_messageMediaDocument(); + const auto ttl = document.vttl_seconds(); + Assert(ttl != nullptr); + + setSelfDestruct(HistoryServiceSelfDestruct::Type::Video, ttl->v); + if (out()) { + setServiceText({ + tr::lng_ttl_video_sent(tr::now, Ui::Text::WithEntities) + }); + } else { + auto result = PreparedServiceText(); + result.links.push_back(fromLink()); + result.text = tr::lng_ttl_video_received( + tr::now, + lt_from, + fromLinkText(), // Link 1. + Ui::Text::WithEntities); + setServiceText(std::move(result)); + } + } else { + setServiceText({ + tr::lng_ttl_video_expired(tr::now, Ui::Text::WithEntities) + }); + } + } break; + + default: Unexpected("Media type in HistoryItem::createServiceFromMtp()"); + } + + if (const auto reactions = message.vreactions()) { + updateReactions(reactions); + } +} + +void HistoryItem::createServiceFromMtp(const MTPDmessageService &message) { + AddComponents(HistoryServiceData::Bit()); + + const auto &action = message.vaction(); + const auto type = action.type(); + if (type == mtpc_messageActionPinMessage) { + UpdateComponents(HistoryServicePinned::Bit()); + } else if (type == mtpc_messageActionTopicCreate + || type == mtpc_messageActionTopicEdit) { + UpdateComponents(HistoryServiceTopicInfo::Bit()); + const auto info = Get(); + info->topicPost = true; + if (type == mtpc_messageActionTopicEdit) { + const auto &data = action.c_messageActionTopicEdit(); + if (const auto title = data.vtitle()) { + info->title = qs(*title); + info->renamed = true; + } + if (const auto icon = data.vicon_emoji_id()) { + info->iconId = icon->v; + info->reiconed = true; + } + if (const auto closed = data.vclosed()) { + info->closed = mtpIsTrue(*closed); + info->reopened = !info->closed; + } + if (const auto hidden = data.vhidden()) { + info->hidden = mtpIsTrue(*hidden); + info->unhidden = !info->hidden; + } + } else { + const auto &data = action.c_messageActionTopicCreate(); + info->title = qs(data.vtitle()); + info->iconId = data.vicon_emoji_id().value_or_empty(); + } + } else if (type == mtpc_messageActionSetChatTheme) { + setupChatThemeChange(); + } else if (type == mtpc_messageActionSetMessagesTTL) { + setupTTLChange(); + } else if (type == mtpc_messageActionGameScore) { + const auto &data = action.c_messageActionGameScore(); + UpdateComponents(HistoryServiceGameScore::Bit()); + Get()->score = data.vscore().v; + } else if (type == mtpc_messageActionPaymentSent) { + const auto &data = action.c_messageActionPaymentSent(); + UpdateComponents(HistoryServicePayment::Bit()); + const auto amount = data.vtotal_amount().v; + const auto currency = qs(data.vcurrency()); + const auto payment = Get(); + const auto id = fullId(); + const auto owner = &_history->owner(); + payment->slug = data.vinvoice_slug().value_or_empty(); + payment->recurringInit = data.is_recurring_init(); + payment->recurringUsed = data.is_recurring_used(); + payment->amount = Ui::FillAmountAndCurrency(amount, currency); + payment->invoiceLink = std::make_shared([=]( + ClickContext context) { + using namespace Payments; + const auto my = context.other.value(); + const auto weak = my.sessionWindow; + if (const auto item = owner->message(id)) { + CheckoutProcess::Start( + item, + Mode::Receipt, + crl::guard(weak, [=](auto) { weak->window().activate(); })); + } + }); + } else if (type == mtpc_messageActionGroupCall + || type == mtpc_messageActionGroupCallScheduled) { + const auto started = (type == mtpc_messageActionGroupCall); + const auto &callData = started + ? action.c_messageActionGroupCall().vcall() + : action.c_messageActionGroupCallScheduled().vcall(); + const auto duration = started + ? action.c_messageActionGroupCall().vduration() + : tl::conditional(); + if (duration) { + RemoveComponents(HistoryServiceOngoingCall::Bit()); + } else { + UpdateComponents(HistoryServiceOngoingCall::Bit()); + const auto call = Get(); + call->id = CallIdFromInput(callData); + call->link = GroupCallClickHandler(_history->peer, call->id); + } + } else if (type == mtpc_messageActionInviteToGroupCall) { + const auto &data = action.c_messageActionInviteToGroupCall(); + const auto id = CallIdFromInput(data.vcall()); + const auto peer = _history->peer; + const auto has = PeerHasThisCall(peer, id); + auto hasLink = !has.has_value() + ? PeerHasThisCallValue(peer, id) + : (*has) + ? PeerHasThisCallValue( + peer, + id) | rpl::skip(1) | rpl::type_erased() + : rpl::producer(); + if (!hasLink) { + RemoveComponents(HistoryServiceOngoingCall::Bit()); + } else { + UpdateComponents(HistoryServiceOngoingCall::Bit()); + const auto call = Get(); + call->id = id; + call->lifetime.destroy(); + + const auto users = data.vusers().v; + std::move(hasLink) | rpl::start_with_next([=](bool has) { + updateServiceText( + prepareInvitedToCallText( + ParseInvitedToCallUsers(this, users), + has ? id : 0)); + if (!has) { + RemoveComponents(HistoryServiceOngoingCall::Bit()); + } + }, call->lifetime); + } + } + if (const auto replyTo = message.vreply_to()) { + replyTo->match([&](const MTPDmessageReplyHeader &data) { + const auto peerId = data.vreply_to_peer_id() + ? peerFromMTP(*data.vreply_to_peer_id()) + : _history->peer->id; + if (const auto dependent = GetServiceDependentData()) { + dependent->peerId = (peerId != _history->peer->id) + ? peerId + : 0; + const auto id = data.vreply_to_msg_id().v; + dependent->msgId = id; + dependent->topId = data.vreply_to_top_id().value_or(id); + dependent->topicPost = data.is_forum_topic() + || Has(); + if (!updateServiceDependent()) { + RequestDependentMessageData( + this, + dependent->peerId, + dependent->msgId); + } + } + }); + } + setServiceMessageByAction(action); +} + +void HistoryItem::setMedia(const MTPMessageMedia &media) { + _media = CreateMedia(this, media); + checkBuyButton(); +} + +void HistoryItem::applyServiceDateEdition(const MTPDmessageService &data) { + const auto date = data.vdate().v; + if (_date == date) { + return; + } + _date = date; +} + +void HistoryItem::setServiceMessageByAction(const MTPmessageAction &action) { + auto prepareChatAddUserText = [this](const MTPDmessageActionChatAddUser &action) { + auto result = PreparedServiceText(); + auto &users = action.vusers().v; + if (users.size() == 1) { + auto u = _history->owner().user(users[0].v); + if (u == _from) { + result.links.push_back(fromLink()); + result.text = tr::lng_action_user_joined( + tr::now, + lt_from, + fromLinkText(), // Link 1. + Ui::Text::WithEntities); + } else { + result.links.push_back(fromLink()); + result.links.push_back(u->createOpenLink()); + result.text = tr::lng_action_add_user( + tr::now, + lt_from, + fromLinkText(), // Link 1. + lt_user, + Ui::Text::Link(u->name(), 2), // Link 2. + Ui::Text::WithEntities); + } + } else if (users.isEmpty()) { + result.links.push_back(fromLink()); + result.text = tr::lng_action_add_user( + tr::now, + lt_from, + fromLinkText(), // Link 1. + lt_user, + { .text = u"somebody"_q }, + Ui::Text::WithEntities); + } else { + result.links.push_back(fromLink()); + for (auto i = 0, l = int(users.size()); i != l; ++i) { + auto user = _history->owner().user(users[i].v); + result.links.push_back(user->createOpenLink()); + + auto linkText = Ui::Text::Link(user->name(), 2 + i); + if (i == 0) { + result.text = linkText; + } else if (i + 1 == l) { + result.text = tr::lng_action_add_users_and_last( + tr::now, + lt_accumulated, + result.text, + lt_user, + linkText, + Ui::Text::WithEntities); + } else { + result.text = tr::lng_action_add_users_and_one( + tr::now, + lt_accumulated, + result.text, + lt_user, + linkText, + Ui::Text::WithEntities); + } + } + result.text = tr::lng_action_add_users_many( + tr::now, + lt_from, + fromLinkText(), // Link 1. + lt_users, + result.text, + Ui::Text::WithEntities); + } + return result; + }; + + auto prepareChatJoinedByLink = [this](const MTPDmessageActionChatJoinedByLink &action) { + auto result = PreparedServiceText(); + result.links.push_back(fromLink()); + result.text = tr::lng_action_user_joined_by_link( + tr::now, + lt_from, + fromLinkText(), // Link 1. + Ui::Text::WithEntities); + return result; + }; + + auto prepareChatCreate = [this](const MTPDmessageActionChatCreate &action) { + auto result = PreparedServiceText(); + result.links.push_back(fromLink()); + result.text = tr::lng_action_created_chat( + tr::now, + lt_from, + fromLinkText(), // Link 1. + lt_title, + { .text = qs(action.vtitle()) }, + Ui::Text::WithEntities); + return result; + }; + + auto prepareChannelCreate = [this](const MTPDmessageActionChannelCreate &action) { + auto result = PreparedServiceText(); + if (isPost()) { + result.text = tr::lng_action_created_channel( + tr::now, + Ui::Text::WithEntities); + } else { + result.links.push_back(fromLink()); + result.text = tr::lng_action_created_chat( + tr::now, + lt_from, + fromLinkText(), // Link 1. + lt_title, + { .text = qs(action.vtitle()) }, + Ui::Text::WithEntities); + } + return result; + }; + + auto prepareChatDeletePhoto = [this] { + auto result = PreparedServiceText(); + if (isPost()) { + result.text = tr::lng_action_removed_photo_channel( + tr::now, + Ui::Text::WithEntities); + } else { + result.links.push_back(fromLink()); + result.text = tr::lng_action_removed_photo( + tr::now, + lt_from, + fromLinkText(), // Link 1. + Ui::Text::WithEntities); + } + return result; + }; + + auto prepareChatDeleteUser = [this](const MTPDmessageActionChatDeleteUser &action) { + auto result = PreparedServiceText(); + if (peerFromUser(action.vuser_id()) == _from->id) { + result.links.push_back(fromLink()); + result.text = tr::lng_action_user_left( + tr::now, + lt_from, + fromLinkText(), // Link 1. + Ui::Text::WithEntities); + } else { + auto user = _history->owner().user(action.vuser_id().v); + result.links.push_back(fromLink()); + result.links.push_back(user->createOpenLink()); + result.text = tr::lng_action_kick_user( + tr::now, + lt_from, + fromLinkText(), // Link 1. + lt_user, + Ui::Text::Link(user->name(), 2), // Link 2. + Ui::Text::WithEntities); + } + return result; + }; + + auto prepareChatEditPhoto = [this](const MTPDmessageActionChatEditPhoto &action) { + auto result = PreparedServiceText(); + if (isPost()) { + result.text = tr::lng_action_changed_photo_channel( + tr::now, + Ui::Text::WithEntities); + } else { + result.links.push_back(fromLink()); + result.text = tr::lng_action_changed_photo( + tr::now, + lt_from, + fromLinkText(), // Link 1. + Ui::Text::WithEntities); + } + return result; + }; + + auto prepareChatEditTitle = [this](const MTPDmessageActionChatEditTitle &action) { + auto result = PreparedServiceText(); + if (isPost()) { + result.text = tr::lng_action_changed_title_channel( + tr::now, + lt_title, + { .text = (qs(action.vtitle())) }, + Ui::Text::WithEntities); + } else { + result.links.push_back(fromLink()); + result.text = tr::lng_action_changed_title( + tr::now, + lt_from, + fromLinkText(), // Link 1. + lt_title, + { .text = qs(action.vtitle()) }, + Ui::Text::WithEntities); + } + return result; + }; + + auto prepareScreenshotTaken = [this] { + auto result = PreparedServiceText(); + if (out()) { + result.text = tr::lng_action_you_took_screenshot( + tr::now, + Ui::Text::WithEntities); + } else { + result.links.push_back(fromLink()); + result.text = tr::lng_action_took_screenshot( + tr::now, + lt_from, + fromLinkText(), // Link 1. + Ui::Text::WithEntities); + } + return result; + }; + + auto prepareCustomAction = [&](const MTPDmessageActionCustomAction &action) { + auto result = PreparedServiceText(); + result.text = { .text = qs(action.vmessage()) }; + return result; + }; + + auto prepareBotAllowed = [&](const MTPDmessageActionBotAllowed &action) { + auto result = PreparedServiceText(); + const auto domain = qs(action.vdomain()); + result.text = tr::lng_action_bot_allowed_from_domain( + tr::now, + lt_domain, + Ui::Text::Link(domain, u"http://"_q + domain), + Ui::Text::WithEntities); + return result; + }; + + auto prepareSecureValuesSent = [&](const MTPDmessageActionSecureValuesSent &action) { + auto result = PreparedServiceText(); + auto documents = QStringList(); + for (const auto &type : action.vtypes().v) { + documents.push_back([&] { + switch (type.type()) { + case mtpc_secureValueTypePersonalDetails: + return tr::lng_action_secure_personal_details(tr::now); + case mtpc_secureValueTypePassport: + case mtpc_secureValueTypeDriverLicense: + case mtpc_secureValueTypeIdentityCard: + case mtpc_secureValueTypeInternalPassport: + return tr::lng_action_secure_proof_of_identity(tr::now); + case mtpc_secureValueTypeAddress: + return tr::lng_action_secure_address(tr::now); + case mtpc_secureValueTypeUtilityBill: + case mtpc_secureValueTypeBankStatement: + case mtpc_secureValueTypeRentalAgreement: + case mtpc_secureValueTypePassportRegistration: + case mtpc_secureValueTypeTemporaryRegistration: + return tr::lng_action_secure_proof_of_address(tr::now); + case mtpc_secureValueTypePhone: + return tr::lng_action_secure_phone(tr::now); + case mtpc_secureValueTypeEmail: + return tr::lng_action_secure_email(tr::now); + } + Unexpected("Type in prepareSecureValuesSent."); + }()); + }; + result.links.push_back(_history->peer->createOpenLink()); + result.text = tr::lng_action_secure_values_sent( + tr::now, + lt_user, + Ui::Text::Link(_history->peer->name(), QString()), // Link 1. + lt_documents, + { .text = documents.join(", ") }, + Ui::Text::WithEntities); + return result; + }; + + auto prepareContactSignUp = [this] { + auto result = PreparedServiceText(); + result.links.push_back(fromLink()); + result.text = tr::lng_action_user_registered( + tr::now, + lt_from, + fromLinkText(), // Link 1. + Ui::Text::WithEntities); + return result; + }; + + auto prepareProximityReached = [this](const MTPDmessageActionGeoProximityReached &action) { + auto result = PreparedServiceText(); + const auto fromId = peerFromMTP(action.vfrom_id()); + const auto fromPeer = _history->owner().peer(fromId); + const auto toId = peerFromMTP(action.vto_id()); + const auto toPeer = _history->owner().peer(toId); + const auto selfId = _from->session().userPeerId(); + const auto distanceMeters = action.vdistance().v; + const auto distance = [&] { + if (distanceMeters >= 1000) { + const auto km = (10 * (distanceMeters / 10)) / 1000.; + return tr::lng_action_proximity_distance_km( + tr::now, + lt_count, + km); + } else { + return tr::lng_action_proximity_distance_m( + tr::now, + lt_count, + distanceMeters); + } + }(); + result.text = [&] { + if (fromId == selfId) { + result.links.push_back(toPeer->createOpenLink()); + return tr::lng_action_you_proximity_reached( + tr::now, + lt_distance, + { .text = distance }, + lt_user, + Ui::Text::Link(toPeer->name(), QString()), // Link 1. + Ui::Text::WithEntities); + } else if (toId == selfId) { + result.links.push_back(fromPeer->createOpenLink()); + return tr::lng_action_proximity_reached_you( + tr::now, + lt_from, + Ui::Text::Link(fromPeer->name(), QString()), // Link 1. + lt_distance, + { .text = distance }, + Ui::Text::WithEntities); + } else { + result.links.push_back(fromPeer->createOpenLink()); + result.links.push_back(toPeer->createOpenLink()); + return tr::lng_action_proximity_reached( + tr::now, + lt_from, + Ui::Text::Link(fromPeer->name(), 1), // Link 1. + lt_distance, + { .text = distance }, + lt_user, + Ui::Text::Link(toPeer->name(), 2), // Link 2. + Ui::Text::WithEntities); + } + }(); + return result; + }; + + auto prepareGroupCall = [this](const MTPDmessageActionGroupCall &action) { + auto result = PreparedServiceText(); + if (const auto duration = action.vduration()) { + const auto seconds = duration->v; + const auto days = seconds / 86400; + const auto hours = seconds / 3600; + const auto minutes = seconds / 60; + auto text = (days > 1) + ? tr::lng_days(tr::now, lt_count, days) + : (hours > 1) + ? tr::lng_hours(tr::now, lt_count, hours) + : (minutes > 1) + ? tr::lng_minutes(tr::now, lt_count, minutes) + : tr::lng_seconds(tr::now, lt_count, seconds); + if (_history->peer->isBroadcast()) { + result.text = tr::lng_action_group_call_finished( + tr::now, + lt_duration, + { .text = text }, + Ui::Text::WithEntities); + } else { + result.links.push_back(fromLink()); + result.text = tr::lng_action_group_call_finished_group( + tr::now, + lt_from, + fromLinkText(), // Link 1. + lt_duration, + { .text = text }, + Ui::Text::WithEntities); + } + return result; + } + if (_history->peer->isBroadcast()) { + result.text = tr::lng_action_group_call_started_channel( + tr::now, + Ui::Text::WithEntities); + } else { + result.links.push_back(fromLink()); + result.text = tr::lng_action_group_call_started_group( + tr::now, + lt_from, + fromLinkText(), // Link 1. + Ui::Text::WithEntities); + } + return result; + }; + + auto prepareInviteToGroupCall = [this](const MTPDmessageActionInviteToGroupCall &action) { + const auto callId = CallIdFromInput(action.vcall()); + const auto owner = &_history->owner(); + const auto peer = _history->peer; + for (const auto &id : action.vusers().v) { + const auto user = owner->user(id.v); + if (callId) { + owner->registerInvitedToCallUser(callId, peer, user); + } + }; + const auto linkCallId = PeerHasThisCall(peer, callId).value_or(false) + ? callId + : 0; + return prepareInvitedToCallText( + ParseInvitedToCallUsers(this, action.vusers().v), + linkCallId); + }; + + auto prepareSetMessagesTTL = [this](const MTPDmessageActionSetMessagesTTL &action) { + auto result = PreparedServiceText(); + const auto period = action.vperiod().v; + const auto duration = (period == 5) + ? u"5 seconds"_q + : Ui::FormatTTL(period); + if (const auto from = action.vauto_setting_from()) { + if (const auto peer = _from->owner().peer(peerFromUser(*from))) { + if (!peer->isSelf() && period) { + result.text = tr::lng_action_ttl_global( + tr::now, + lt_from, + fromLinkText(), // Link 1. + lt_duration, + { .text = duration }, + Ui::Text::WithEntities); + return result; + } + } + } + if (isPost()) { + if (!period) { + result.text = tr::lng_action_ttl_removed_channel( + tr::now, + Ui::Text::WithEntities); + } else { + result.text = tr::lng_action_ttl_changed_channel( + tr::now, + lt_duration, + { .text = duration }, + Ui::Text::WithEntities); + } + } else if (_from->isSelf()) { + if (!period) { + result.text = tr::lng_action_ttl_removed_you( + tr::now, + Ui::Text::WithEntities); + } else { + result.text = tr::lng_action_ttl_changed_you( + tr::now, + lt_duration, + { .text = duration }, + Ui::Text::WithEntities); + } + } else { + result.links.push_back(fromLink()); + if (!period) { + result.text = tr::lng_action_ttl_removed( + tr::now, + lt_from, + fromLinkText(), // Link 1. + Ui::Text::WithEntities); + } else { + result.text = tr::lng_action_ttl_changed( + tr::now, + lt_from, + fromLinkText(), // Link 1. + lt_duration, + { .text = duration }, + Ui::Text::WithEntities); + } + } + return result; + }; + + auto prepareSetChatTheme = [this](const MTPDmessageActionSetChatTheme &action) { + auto result = PreparedServiceText(); + const auto text = qs(action.vemoticon()); + if (!text.isEmpty()) { + if (_from->isSelf()) { + result.text = tr::lng_action_you_theme_changed( + tr::now, + lt_emoji, + { .text = text }, + Ui::Text::WithEntities); + } else { + result.links.push_back(fromLink()); + result.text = tr::lng_action_theme_changed( + tr::now, + lt_from, + fromLinkText(), // Link 1. + lt_emoji, + { .text = text }, + Ui::Text::WithEntities); + } + } else { + if (_from->isSelf()) { + result.text = tr::lng_action_you_theme_disabled( + tr::now, + Ui::Text::WithEntities); + } else { + result.links.push_back(fromLink()); + result.text = tr::lng_action_theme_disabled( + tr::now, + lt_from, + fromLinkText(), // Link 1. + Ui::Text::WithEntities); + } + } + return result; + }; + + auto prepareChatJoinedByRequest = [this](const MTPDmessageActionChatJoinedByRequest &action) { + auto result = PreparedServiceText(); + result.links.push_back(fromLink()); + result.text = tr::lng_action_user_joined_by_request( + tr::now, + lt_from, + fromLinkText(), // Link 1. + Ui::Text::WithEntities); + return result; + }; + + auto prepareWebViewDataSent = [](const MTPDmessageActionWebViewDataSent &action) { + auto result = PreparedServiceText(); + result.text = tr::lng_action_webview_data_done( + tr::now, + lt_text, + { .text = qs(action.vtext()) }, + Ui::Text::WithEntities); + return result; + }; + + auto prepareGiftPremium = [&]( + const MTPDmessageActionGiftPremium &action) { + auto result = PreparedServiceText(); + const auto isSelf = (_from->id == _from->session().userPeerId()); + const auto peer = isSelf ? _history->peer : _from; + _history->session().giftBoxStickersPacks().load(); + const auto amount = action.vamount().v; + const auto currency = qs(action.vcurrency()); + result.links.push_back(peer->createOpenLink()); + result.text = (isSelf + ? tr::lng_action_gift_received_me + : tr::lng_action_gift_received)( + tr::now, + lt_user, + Ui::Text::Link(peer->name(), 1), // Link 1. + lt_cost, + { Ui::FillAmountAndCurrency(amount, currency) }, + Ui::Text::WithEntities); + return result; + }; + + auto prepareTopicCreate = [&](const MTPDmessageActionTopicCreate &action) { + auto result = PreparedServiceText(); + const auto topicUrl = u"internal:url:https://t.me/c/%1/%2"_q + .arg(peerToChannel(_history->peer->id).bare) + .arg(id.bare); + result.text = tr::lng_action_topic_created( + tr::now, + lt_topic, + Ui::Text::Link( + Data::ForumTopicIconWithTitle( + id, + action.vicon_emoji_id().value_or_empty(), + qs(action.vtitle())), + topicUrl), + Ui::Text::WithEntities); + return result; + }; + + auto prepareTopicEdit = [&](const MTPDmessageActionTopicEdit &action) { + auto result = PreparedServiceText(); + if (const auto closed = action.vclosed()) { + result.text = { mtpIsTrue(*closed) + ? tr::lng_action_topic_closed_inside(tr::now) + : tr::lng_action_topic_reopened_inside(tr::now) }; + } else if (const auto hidden = action.vhidden()) { + result.text = { mtpIsTrue(*hidden) + ? tr::lng_action_topic_hidden_inside(tr::now) + : tr::lng_action_topic_unhidden_inside(tr::now) }; + } else if (!action.vtitle()) { + if (const auto icon = action.vicon_emoji_id()) { + if (const auto iconId = icon->v) { + result.links.push_back(fromLink()); + result.text = tr::lng_action_topic_icon_changed( + tr::now, + lt_from, + fromLinkText(), // Link 1. + lt_link, + { tr::lng_action_topic_placeholder(tr::now) }, + lt_emoji, + Data::SingleCustomEmoji(iconId), + Ui::Text::WithEntities); + } else { + result.links.push_back(fromLink()); + result.text = tr::lng_action_topic_icon_removed( + tr::now, + lt_from, + fromLinkText(), // Link 1. + lt_link, + { tr::lng_action_topic_placeholder(tr::now) }, + Ui::Text::WithEntities); + } + } + } else { + result.links.push_back(fromLink()); + result.text = tr::lng_action_topic_renamed( + tr::now, + lt_from, + fromLinkText(), // Link 1. + lt_link, + { tr::lng_action_topic_placeholder(tr::now) }, + lt_title, + Data::ForumTopicIconWithTitle( + topicRootId(), + action.vicon_emoji_id().value_or_empty(), + qs(*action.vtitle())), + Ui::Text::WithEntities); + } + if (result.text.empty()) { + result.text = { tr::lng_message_empty(tr::now) }; + } + return result; + }; + + setServiceText(action.match([&]( + const MTPDmessageActionChatAddUser &data) { + return prepareChatAddUserText(data); + }, [&](const MTPDmessageActionChatJoinedByLink &data) { + return prepareChatJoinedByLink(data); + }, [&](const MTPDmessageActionChatCreate &data) { + return prepareChatCreate(data); + }, [](const MTPDmessageActionChatMigrateTo &) { + return PreparedServiceText(); + }, [](const MTPDmessageActionChannelMigrateFrom &) { + return PreparedServiceText(); + }, [](const MTPDmessageActionHistoryClear &) { + return PreparedServiceText(); + }, [&](const MTPDmessageActionChannelCreate &data) { + return prepareChannelCreate(data); + }, [&](const MTPDmessageActionChatDeletePhoto &) { + return prepareChatDeletePhoto(); + }, [&](const MTPDmessageActionChatDeleteUser &data) { + return prepareChatDeleteUser(data); + }, [&](const MTPDmessageActionChatEditPhoto &data) { + return prepareChatEditPhoto(data); + }, [&](const MTPDmessageActionChatEditTitle &data) { + return prepareChatEditTitle(data); + }, [&](const MTPDmessageActionPinMessage &) { + return preparePinnedText(); + }, [&](const MTPDmessageActionGameScore &) { + return prepareGameScoreText(); + }, [&](const MTPDmessageActionPhoneCall &) -> PreparedServiceText { + Unexpected("PhoneCall type in setServiceMessageFromMtp."); + }, [&](const MTPDmessageActionPaymentSent &) { + return preparePaymentSentText(); + }, [&](const MTPDmessageActionScreenshotTaken &) { + return prepareScreenshotTaken(); + }, [&](const MTPDmessageActionCustomAction &data) { + return prepareCustomAction(data); + }, [&](const MTPDmessageActionBotAllowed &data) { + return prepareBotAllowed(data); + }, [&](const MTPDmessageActionSecureValuesSent &data) { + return prepareSecureValuesSent(data); + }, [&](const MTPDmessageActionContactSignUp &data) { + return prepareContactSignUp(); + }, [&](const MTPDmessageActionGeoProximityReached &data) { + return prepareProximityReached(data); + }, [](const MTPDmessageActionPaymentSentMe &) { + LOG(("API Error: messageActionPaymentSentMe received.")); + return PreparedServiceText{ { tr::lng_message_empty(tr::now) } }; + }, [](const MTPDmessageActionSecureValuesSentMe &) { + LOG(("API Error: messageActionSecureValuesSentMe received.")); + return PreparedServiceText{ { tr::lng_message_empty(tr::now) } }; + }, [&](const MTPDmessageActionGroupCall &data) { + return prepareGroupCall(data); + }, [&](const MTPDmessageActionInviteToGroupCall &data) { + return prepareInviteToGroupCall(data); + }, [&](const MTPDmessageActionSetMessagesTTL &data) { + return prepareSetMessagesTTL(data); + }, [&](const MTPDmessageActionGroupCallScheduled &data) { + return prepareCallScheduledText(data.vschedule_date().v); + }, [&](const MTPDmessageActionSetChatTheme &data) { + return prepareSetChatTheme(data); + }, [&](const MTPDmessageActionChatJoinedByRequest &data) { + return prepareChatJoinedByRequest(data); + }, [&](const MTPDmessageActionWebViewDataSent &data) { + return prepareWebViewDataSent(data); + }, [&](const MTPDmessageActionGiftPremium &data) { + return prepareGiftPremium(data); + }, [&](const MTPDmessageActionTopicCreate &data) { + return prepareTopicCreate(data); + }, [&](const MTPDmessageActionTopicEdit &data) { + return prepareTopicEdit(data); + }, [&](const MTPDmessageActionWebViewDataSentMe &data) { + LOG(("API Error: messageActionWebViewDataSentMe received.")); + return PreparedServiceText{ { tr::lng_message_empty(tr::now) } }; + }, [](const MTPDmessageActionEmpty &) { + return PreparedServiceText{ { tr::lng_message_empty(tr::now) } }; + })); + + // Additional information. + applyAction(action); +} + +void HistoryItem::applyAction(const MTPMessageAction &action) { + action.match([&](const MTPDmessageActionChatAddUser &data) { + if (const auto channel = _history->peer->asMegagroup()) { + const auto selfUserId = _history->session().userId(); + for (const auto &item : data.vusers().v) { + if (peerFromUser(item) == selfUserId) { + channel->mgInfo->joinedMessageFound = true; + break; + } + } + } + }, [&](const MTPDmessageActionChatJoinedByLink &data) { + if (_from->isSelf()) { + if (const auto channel = _history->peer->asMegagroup()) { + channel->mgInfo->joinedMessageFound = true; + } + } + }, [&](const MTPDmessageActionChatEditPhoto &data) { + data.vphoto().match([&](const MTPDphoto &photo) { + _media = std::make_unique( + this, + _history->peer, + _history->owner().processPhoto(photo)); + }, [](const MTPDphotoEmpty &) { + }); + }, [&](const MTPDmessageActionChatCreate &) { + _flags |= MessageFlag::IsGroupEssential; + }, [&](const MTPDmessageActionChannelCreate &) { + _flags |= MessageFlag::IsGroupEssential; + }, [&](const MTPDmessageActionChatMigrateTo &) { + _flags |= MessageFlag::IsGroupEssential; + }, [&](const MTPDmessageActionChannelMigrateFrom &) { + _flags |= MessageFlag::IsGroupEssential; + }, [&](const MTPDmessageActionContactSignUp &) { + _flags |= MessageFlag::IsContactSignUp; + }, [&](const MTPDmessageActionChatJoinedByRequest &data) { + if (_from->isSelf()) { + if (const auto channel = _history->peer->asMegagroup()) { + channel->mgInfo->joinedMessageFound = true; + } + } + }, [&](const MTPDmessageActionGiftPremium &data) { + _media = std::make_unique( + this, + _from, + data.vmonths().v); + }, [](const auto &) { + }); +} + +void HistoryItem::setSelfDestruct(HistoryServiceSelfDestruct::Type type, int ttlSeconds) { + UpdateComponents(HistoryServiceSelfDestruct::Bit()); + auto selfdestruct = Get(); + selfdestruct->timeToLive = ttlSeconds * 1000LL; + selfdestruct->type = type; +} + +PreparedServiceText HistoryItem::prepareInvitedToCallText( + const std::vector> &users, + CallId linkCallId) { + const auto owner = &_history->owner(); + auto chatText = tr::lng_action_invite_user_chat( + tr::now, + Ui::Text::WithEntities); + auto result = PreparedServiceText(); + result.links.push_back(fromLink()); + auto linkIndex = 1; + if (linkCallId) { + const auto peer = _history->peer; + result.links.push_back(GroupCallClickHandler(peer, linkCallId)); + chatText = Ui::Text::Link(chatText.text, ++linkIndex); + } + if (users.size() == 1) { + auto user = users[0]; + result.links.push_back(user->createOpenLink()); + result.text = tr::lng_action_invite_user( + tr::now, + lt_from, + fromLinkText(), // Link 1. + lt_user, + Ui::Text::Link(user->name(), ++linkIndex), // Link N. + lt_chat, + chatText, + Ui::Text::WithEntities); + } else if (users.empty()) { + result.text = tr::lng_action_invite_user( + tr::now, + lt_from, + fromLinkText(), // Link 1. + lt_user, + { .text = u"somebody"_q }, + lt_chat, + chatText, + Ui::Text::WithEntities); + } else { + for (auto i = 0, l = int(users.size()); i != l; ++i) { + const auto user = users[i]; + result.links.push_back(user->createOpenLink()); + + auto linkText = Ui::Text::Link(user->name(), ++linkIndex); + if (i == 0) { + result.text = linkText; + } else if (i + 1 == l) { + result.text = tr::lng_action_invite_users_and_last( + tr::now, + lt_accumulated, + result.text, + lt_user, + linkText, + Ui::Text::WithEntities); + } else { + result.text = tr::lng_action_invite_users_and_one( + tr::now, + lt_accumulated, + result.text, + lt_user, + linkText, + Ui::Text::WithEntities); + } + } + result.text = tr::lng_action_invite_users_many( + tr::now, + lt_from, + fromLinkText(), // Link 1. + lt_users, + result.text, + lt_chat, + chatText, + Ui::Text::WithEntities); + } + return result; +} + +PreparedServiceText HistoryItem::preparePinnedText() { + auto result = PreparedServiceText(); + auto pinned = Get(); + if (pinned && pinned->msg) { + const auto mediaText = [&] { + using TTL = HistoryServiceSelfDestruct; + if (const auto media = pinned->msg->media()) { + return media->pinnedTextSubstring(); + } else if (const auto selfdestruct = pinned->msg->Get()) { + if (selfdestruct->type == TTL::Type::Photo) { + return tr::lng_action_pinned_media_photo(tr::now); + } else if (selfdestruct->type == TTL::Type::Video) { + return tr::lng_action_pinned_media_video(tr::now); + } + } + return QString(); + }(); + result.links.push_back(fromLink()); + result.links.push_back(pinned->lnk); + if (mediaText.isEmpty()) { + auto original = pinned->msg->originalText(); + auto cutAt = 0; + auto limit = kPinnedMessageTextLimit; + auto size = original.text.size(); + for (; limit != 0;) { + --limit; + if (cutAt >= size) break; + if (original.text.at(cutAt).isLowSurrogate() + && (cutAt + 1 < size) + && original.text.at(cutAt + 1).isHighSurrogate()) { + cutAt += 2; + } else { + ++cutAt; + } + } + if (!limit && cutAt + 5 < size) { + original = Ui::Text::Mid(original, 0, cutAt).append( + Ui::kQEllipsis); + } + original = Ui::Text::Link( + Ui::Text::Filtered( + std::move(original), + { + EntityType::Spoiler, + EntityType::StrikeOut, + EntityType::Italic, + EntityType::CustomEmoji, + }), + 2); + result.text = tr::lng_action_pinned_message( + tr::now, + lt_from, + fromLinkText(), // Link 1. + lt_text, + std::move(original), // Link 2. + Ui::Text::WithEntities); + } else { + result.text = tr::lng_action_pinned_media( + tr::now, + lt_from, + fromLinkText(), // Link 1. + lt_media, + Ui::Text::Link(mediaText, 2), // Link 2. + Ui::Text::WithEntities); + } + } else if (pinned && pinned->msgId) { + result.links.push_back(fromLink()); + result.links.push_back(pinned->lnk); + result.text = tr::lng_action_pinned_media( + tr::now, + lt_from, + fromLinkText(), // Link 1. + lt_media, + Ui::Text::Link(tr::lng_contacts_loading(tr::now), 2), // Link 2. + Ui::Text::WithEntities); + } else { + result.links.push_back(fromLink()); + result.text = tr::lng_action_pinned_media( + tr::now, + lt_from, + fromLinkText(), // Link 1. + lt_media, + { .text = tr::lng_deleted_message(tr::now) }, + Ui::Text::WithEntities); + } + return result; +} + +PreparedServiceText HistoryItem::prepareGameScoreText() { + auto result = PreparedServiceText(); + auto gamescore = Get(); + + auto computeGameTitle = [&]() -> TextWithEntities { + if (gamescore && gamescore->msg) { + if (const auto media = gamescore->msg->media()) { + if (const auto game = media->game()) { + const auto row = 0; + const auto column = 0; + result.links.push_back( + std::make_shared( + &_history->owner(), + row, + column, + gamescore->msg->fullId())); + auto titleText = game->title; + return Ui::Text::Link(titleText, QString()); + } + } + return tr::lng_deleted_message(tr::now, Ui::Text::WithEntities); + } else if (gamescore && gamescore->msgId) { + return tr::lng_contacts_loading(tr::now, Ui::Text::WithEntities); + } + return {}; + }; + + const auto scoreNumber = gamescore ? gamescore->score : 0; + if (_from->isSelf()) { + auto gameTitle = computeGameTitle(); + if (gameTitle.text.isEmpty()) { + result.text = tr::lng_action_game_you_scored_no_game( + tr::now, + lt_count, + scoreNumber, + Ui::Text::WithEntities); + } else { + result.text = tr::lng_action_game_you_scored( + tr::now, + lt_count, + scoreNumber, + lt_game, + gameTitle, + Ui::Text::WithEntities); + } + } else { + result.links.push_back(fromLink()); + auto gameTitle = computeGameTitle(); + if (gameTitle.text.isEmpty()) { + result.text = tr::lng_action_game_score_no_game( + tr::now, + lt_count, + scoreNumber, + lt_from, + fromLinkText(), // Link 1. + Ui::Text::WithEntities); + } else { + result.text = tr::lng_action_game_score( + tr::now, + lt_count, + scoreNumber, + lt_from, + fromLinkText(), // Link 1. + lt_game, + gameTitle, + Ui::Text::WithEntities); + } + } + return result; +} + +PreparedServiceText HistoryItem::preparePaymentSentText() { + auto result = PreparedServiceText(); + const auto payment = Get(); + Assert(payment != nullptr); + + auto invoiceTitle = [&] { + if (payment->msg) { + if (const auto media = payment->msg->media()) { + if (const auto invoice = media->invoice()) { + return Ui::Text::Link(invoice->title, QString()); + } + } + } + return TextWithEntities(); + }(); + + if (invoiceTitle.text.isEmpty()) { + if (payment->recurringUsed) { + result.text = tr::lng_action_payment_used_recurring( + tr::now, + lt_amount, + { .text = payment->amount }, + Ui::Text::WithEntities); + } else { + result.text = (payment->recurringInit + ? tr::lng_action_payment_init_recurring + : tr::lng_action_payment_done)( + tr::now, + lt_amount, + { .text = payment->amount }, + lt_user, + { .text = _history->peer->name() }, + Ui::Text::WithEntities); + } + } else { + result.text = (payment->recurringInit + ? tr::lng_action_payment_init_recurring_for + : tr::lng_action_payment_done_for)( + tr::now, + lt_amount, + { .text = payment->amount }, + lt_user, + { .text = _history->peer->name() }, + lt_invoice, + invoiceTitle, + Ui::Text::WithEntities); + if (payment->msg) { + result.links.push_back(payment->lnk); + } + } + return result; +} + +PreparedServiceText HistoryItem::prepareCallScheduledText( + TimeId scheduleDate) { + const auto call = Get(); + Assert(call != nullptr); + + const auto scheduled = base::unixtime::parse(scheduleDate); + const auto date = scheduled.date(); + const auto now = QDateTime::currentDateTime(); + const auto secsToDateAddDays = [&](int days) { + return now.secsTo(QDateTime(date.addDays(days), QTime(0, 0))); + }; + auto result = PreparedServiceText(); + const auto prepareWithDate = [&](const QString &date) { + if (_history->peer->isBroadcast()) { + result.text = tr::lng_action_group_call_scheduled_channel( + tr::now, + lt_date, + { .text = date }, + Ui::Text::WithEntities); + } else { + result.links.push_back(fromLink()); + result.text = tr::lng_action_group_call_scheduled_group( + tr::now, + lt_from, + fromLinkText(), // Link 1. + lt_date, + { .text = date }, + Ui::Text::WithEntities); + } + }; + const auto time = QLocale().toString(scheduled.time(), cTimeFormat()); + const auto prepareGeneric = [&] { + prepareWithDate(tr::lng_group_call_starts_date( + tr::now, + lt_date, + langDayOfMonthFull(date), + lt_time, + time)); + }; + auto nextIn = TimeId(0); + if (now.date().addDays(1) < scheduled.date()) { + nextIn = secsToDateAddDays(-1); + prepareGeneric(); + } else if (now.date().addDays(1) == scheduled.date()) { + nextIn = secsToDateAddDays(0); + prepareWithDate( + tr::lng_group_call_starts_tomorrow(tr::now, lt_time, time)); + } else if (now.date() == scheduled.date()) { + nextIn = secsToDateAddDays(1); + prepareWithDate( + tr::lng_group_call_starts_today(tr::now, lt_time, time)); + } else { + prepareGeneric(); + } + if (nextIn) { + call->lifetime = base::timer_once( + (nextIn + 2) * crl::time(1000) + ) | rpl::start_with_next([=] { + updateServiceText(prepareCallScheduledText(scheduleDate)); + }); + } + return result; +} + +TextWithEntities HistoryItem::fromLinkText() const { + return Ui::Text::Link(_from->name(), 1); +} + +ClickHandlerPtr HistoryItem::fromLink() const { + return _from->createOpenLink(); +} + +crl::time HistoryItem::getSelfDestructIn(crl::time now) { + if (auto selfdestruct = Get()) { + if (selfdestruct->destructAt > 0) { + if (selfdestruct->destructAt <= now) { + auto text = [selfdestruct] { + switch (selfdestruct->type) { + case HistoryServiceSelfDestruct::Type::Photo: + return tr::lng_ttl_photo_expired(tr::now); + case HistoryServiceSelfDestruct::Type::Video: + return tr::lng_ttl_video_expired(tr::now); + } + Unexpected("Type in HistoryServiceSelfDestruct::Type"); + }; + setServiceText({ TextWithEntities{.text = text() } }); + return 0; + } + return selfdestruct->destructAt - now; + } + } + return 0; +} + +void HistoryItem::setupChatThemeChange() { + if (const auto user = history()->peer->asUser()) { + auto link = std::make_shared([=]( + ClickContext context) { + const auto my = context.other.value(); + if (const auto controller = my.sessionWindow.get()) { + controller->toggleChooseChatTheme(user); + } + }); + + UpdateComponents(HistoryServiceChatThemeChange::Bit()); + Get()->link = std::move(link); + } else { + RemoveComponents(HistoryServiceChatThemeChange::Bit()); + } +} + +void HistoryItem::setupTTLChange() { + const auto peer = history()->peer; + auto link = std::make_shared([=]( + ClickContext context) { + const auto my = context.other.value(); + if (const auto controller = my.sessionWindow.get()) { + const auto validator = TTLMenu::TTLValidator( + std::make_shared(controller), + peer); + if (validator.can()) { + validator.showBox(); } } }); + + UpdateComponents(HistoryServiceTTLChange::Bit()); + Get()->link = std::move(link); } -MessageFlags FlagsFromMTP( - MsgId id, - MTPDmessage::Flags flags, - MessageFlags localFlags) { - using Flag = MessageFlag; - using MTP = MTPDmessage::Flag; - return localFlags - | (IsServerMsgId(id) ? Flag::HistoryEntry : Flag()) - | ((flags & MTP::f_out) ? Flag::Outgoing : Flag()) - | ((flags & MTP::f_mentioned) ? Flag::MentionsMe : Flag()) - | ((flags & MTP::f_media_unread) ? Flag::MediaIsUnread : Flag()) - | ((flags & MTP::f_silent) ? Flag::Silent : Flag()) - | ((flags & MTP::f_post) ? Flag::Post : Flag()) - | ((flags & MTP::f_legacy) ? Flag::Legacy : Flag()) - | ((flags & MTP::f_edit_hide) ? Flag::HideEdited : Flag()) - | ((flags & MTP::f_pinned) ? Flag::Pinned : Flag()) - | ((flags & MTP::f_from_id) ? Flag::HasFromId : Flag()) - | ((flags & MTP::f_reply_to) ? Flag::HasReplyInfo : Flag()) - | ((flags & MTP::f_reply_markup) ? Flag::HasReplyMarkup : Flag()) - | ((flags & MTP::f_from_scheduled) ? Flag::IsOrWasScheduled : Flag()) - | ((flags & MTP::f_views) ? Flag::HasViews : Flag()) - | ((flags & MTP::f_noforwards) ? Flag::NoForwards : Flag()); -} - -MessageFlags FlagsFromMTP( - MsgId id, - MTPDmessageService::Flags flags, - MessageFlags localFlags) { - using Flag = MessageFlag; - using MTP = MTPDmessageService::Flag; - return localFlags - | (IsServerMsgId(id) ? Flag::HistoryEntry : Flag()) - | ((flags & MTP::f_out) ? Flag::Outgoing : Flag()) - | ((flags & MTP::f_mentioned) ? Flag::MentionsMe : Flag()) - | ((flags & MTP::f_media_unread) ? Flag::MediaIsUnread : Flag()) - | ((flags & MTP::f_silent) ? Flag::Silent : Flag()) - | ((flags & MTP::f_post) ? Flag::Post : Flag()) - | ((flags & MTP::f_legacy) ? Flag::Legacy : Flag()) - | ((flags & MTP::f_from_id) ? Flag::HasFromId : Flag()) - | ((flags & MTP::f_reply_to) ? Flag::HasReplyInfo : Flag()); -} - -not_null HistoryItem::Create( - not_null history, - MsgId id, - const MTPMessage &message, - MessageFlags localFlags) { - return message.match([&](const MTPDmessage &data) -> HistoryItem* { - const auto media = data.vmedia(); - const auto checked = media - ? CheckMessageMedia(*media) - : MediaCheckResult::Good; - if (checked == MediaCheckResult::Unsupported) { - return CreateUnsupportedMessage( - history, - id, - FlagsFromMTP(id, data.vflags().v, localFlags), - MsgId(0), // No need to pass reply_to data here. - data.vvia_bot_id().value_or_empty(), - data.vdate().v, - data.vfrom_id() ? peerFromMTP(*data.vfrom_id()) : PeerId(0)); - } else if (checked == MediaCheckResult::Empty) { - auto text = HistoryService::PreparedText{ - tr::lng_message_empty(tr::now, Ui::Text::WithEntities) - }; - return history->makeServiceMessage( - id, - FlagsFromMTP(id, data.vflags().v, localFlags), - data.vdate().v, - std::move(text), - data.vfrom_id() ? peerFromMTP(*data.vfrom_id()) : PeerId(0)); - } else if (checked == MediaCheckResult::HasTimeToLive) { - return history->makeServiceMessage(id, data, localFlags); +void HistoryItem::clearDependencyMessage() { + if (const auto dependent = GetServiceDependentData()) { + if (dependent->msg) { + _history->owner().unregisterDependentMessage( + this, + dependent->msg); + dependent->msg = nullptr; + dependent->msgId = 0; } - return history->makeMessage(id, data, localFlags); - }, [&](const MTPDmessageService &data) -> HistoryItem* { - if (data.vaction().type() == mtpc_messageActionPhoneCall) { - return history->makeMessage(id, data, localFlags); - } - return history->makeServiceMessage(id, data, localFlags); - }, [&](const MTPDmessageEmpty &data) -> HistoryItem* { - return history->makeServiceMessage( - id, - localFlags, - TimeId(0), - HistoryService::PreparedText{ tr::lng_message_empty( - tr::now, - Ui::Text::WithEntities) }); - }); + } } diff --git a/Telegram/SourceFiles/history/history_item.h b/Telegram/SourceFiles/history/history_item.h index 590eafa44..b722c9646 100644 --- a/Telegram/SourceFiles/history/history_item.h +++ b/Telegram/SourceFiles/history/history_item.h @@ -15,9 +15,16 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include +class HiddenSenderInfo; +class History; +struct HistoryMessageReply; +struct HistoryMessageViews; +struct HistoryMessageMarkupData; struct HistoryMessageReplyMarkup; +struct HistoryServiceDependentData; +enum class HistorySelfDestructType; +struct PreparedServiceText; class ReplyKeyboard; -class HistoryMessage; namespace base { template @@ -47,6 +54,7 @@ struct MessageReaction; class MessageReactions; class ForumTopic; class Thread; +struct SponsoredFrom; } // namespace Data namespace Main { @@ -68,46 +76,120 @@ enum class CursorState : char; enum class PointState : char; enum class Context : char; class ElementDelegate; +class Element; +class Message; +class Service; +class ServiceMessagePainter; } // namespace HistoryView -class HiddenSenderInfo; -class History; - -[[nodiscard]] MessageFlags FlagsFromMTP( - MsgId id, - MTPDmessage::Flags flags, - MessageFlags localFlags); -[[nodiscard]] MessageFlags FlagsFromMTP( - MsgId id, - MTPDmessageService::Flags flags, - MessageFlags localFlags); - -class HistoryItem : public RuntimeComposer { +class HistoryItem final : public RuntimeComposer { public: - static not_null Create( + [[nodiscard]] static std::unique_ptr CreateMedia( + not_null item, + const MTPMessageMedia &media); + + HistoryItem( not_null history, MsgId id, - const MTPMessage &message, + const MTPDmessage &data, MessageFlags localFlags); + HistoryItem( + not_null history, + MsgId id, + const MTPDmessageService &data, + MessageFlags localFlags); + HistoryItem( + not_null history, + MsgId id, + const MTPDmessageEmpty &data, + MessageFlags localFlags); + + HistoryItem( // Sponsored message. + not_null history, + MsgId id, + Data::SponsoredFrom from, + const TextWithEntities &textWithEntities, + HistoryItem *injectedAfter); + + HistoryItem( // Local message. + not_null history, + MsgId id, + MessageFlags flags, + MsgId replyTo, + UserId viaBotId, + TimeId date, + PeerId from, + const QString &postAuthor, + const TextWithEntities &textWithEntities, + const MTPMessageMedia &media, + HistoryMessageMarkupData &&markup, + uint64 groupedId); + HistoryItem( // Local service message. + not_null history, + MsgId id, + MessageFlags flags, + TimeId date, + PreparedServiceText &&message, + PeerId from = 0, + PhotoData *photo = nullptr); + HistoryItem( // Local forwarded. + not_null history, + MsgId id, + MessageFlags flags, + TimeId date, + PeerId from, + const QString &postAuthor, + not_null original, + MsgId topicRootId); + HistoryItem( // Local photo. + not_null history, + MsgId id, + MessageFlags flags, + MsgId replyTo, + UserId viaBotId, + TimeId date, + PeerId from, + const QString &postAuthor, + not_null photo, + const TextWithEntities &caption, + HistoryMessageMarkupData &&markup); + HistoryItem( // Local document. + not_null history, + MsgId id, + MessageFlags flags, + MsgId replyTo, + UserId viaBotId, + TimeId date, + PeerId from, + const QString &postAuthor, + not_null document, + const TextWithEntities &caption, + HistoryMessageMarkupData &&markup); + HistoryItem( // Local game. + not_null history, + MsgId id, + MessageFlags flags, + MsgId replyTo, + UserId viaBotId, + TimeId date, + PeerId from, + const QString &postAuthor, + not_null game, + HistoryMessageMarkupData &&markup); + ~HistoryItem(); struct Destroyer { void operator()(HistoryItem *value); }; - virtual void dependencyItemRemoved(HistoryItem *dependency) { - } - virtual bool updateDependencyItem() { - return true; - } - virtual MsgId dependencyMsgId() const { - return 0; - } - virtual void checkBuyButton() { - } - [[nodiscard]] virtual bool notificationReady() const { - return true; - } + void dependencyItemRemoved(not_null dependency); + void updateDependencyItem(); + [[nodiscard]] MsgId dependencyMsgId() const; + [[nodiscard]] bool notificationReady() const; [[nodiscard]] PeerData *specialNotificationPeer() const; + void checkBuyButton(); + + void updateServiceText(PreparedServiceText &&text); [[nodiscard]] UserData *viaBot() const; [[nodiscard]] UserData *getMessageBot() const; @@ -165,15 +247,13 @@ public: void setIsPinned(bool isPinned); // For edit media in history_message. - virtual void returnSavedMedia(); + void returnSavedMedia(); void savePreviousMedia(); [[nodiscard]] bool isEditingMedia() const; void clearSavedMedia(); // Zero result means this message is not self-destructing right now. - virtual crl::time getSelfDestructIn(crl::time now) { - return 0; - } + [[nodiscard]] crl::time getSelfDestructIn(crl::time now); [[nodiscard]] bool definesReplyKeyboard() const; [[nodiscard]] ReplyMarkupFlags replyKeyboardFlags() const; @@ -224,118 +304,75 @@ public: [[nodiscard]] bool isRegular() const; [[nodiscard]] bool isUploading() const; void sendFailed(); - [[nodiscard]] virtual int viewsCount() const { - return hasViews() ? 1 : -1; - } - [[nodiscard]] virtual int repliesCount() const { - return 0; - } - [[nodiscard]] virtual bool repliesAreComments() const { - return false; - } - [[nodiscard]] virtual bool externalReply() const { - return false; - } + [[nodiscard]] int viewsCount() const; + [[nodiscard]] int repliesCount() const; + [[nodiscard]] bool repliesAreComments() const; + [[nodiscard]] bool externalReply() const; [[nodiscard]] bool hasExtendedMediaPreview() const; - virtual void setCommentsInboxReadTill(MsgId readTillId) { - } - virtual void setCommentsMaxId(MsgId maxId) { - } - virtual void setCommentsPossibleMaxId(MsgId possibleMaxId) { - } - [[nodiscard]] virtual bool areCommentsUnread() const { - return false; - } + void setCommentsInboxReadTill(MsgId readTillId); + void setCommentsMaxId(MsgId maxId); + void setCommentsPossibleMaxId(MsgId possibleMaxId); + [[nodiscard]] bool areCommentsUnread() const; - [[nodiscard]] virtual FullMsgId commentsItemId() const { - return FullMsgId(); - } - virtual void setCommentsItemId(FullMsgId id) { - } + [[nodiscard]] FullMsgId commentsItemId() const; + void setCommentsItemId(FullMsgId id); - [[nodiscard]] virtual bool needCheck() const; + [[nodiscard]] bool needCheck() const; - [[nodiscard]] virtual bool isService() const { - return false; - } - virtual void applyEdition(HistoryMessageEdition &&edition) { - } - virtual void applyEdition(const MTPDmessageService &message) { - } - virtual void applyEdition(const MTPMessageExtendedMedia &media) { - } - void applyEditionToHistoryCleared(); - virtual void updateSentContent( + [[nodiscard]] bool isService() const; + void applyEdition(HistoryMessageEdition &&edition); + + void applyEdition(const MTPDmessageService &message); + void applyEdition(const MTPMessageExtendedMedia &media); + void updateForwardedInfo(const MTPMessageFwdHeader *fwd); + void updateSentContent( const TextWithEntities &textWithEntities, - const MTPMessageMedia *media) { - } - virtual void updateReplyMarkup(HistoryMessageMarkupData &&markup) { - } - virtual void updateForwardedInfo(const MTPMessageFwdHeader *fwd) { - } - virtual void contributeToSlowmode(TimeId realDate = 0) { - } - - virtual void addToUnreadThings(HistoryUnreadThings::AddType type); - virtual void destroyHistoryEntry() { - } - [[nodiscard]] virtual Storage::SharedMediaTypesMask sharedMediaTypes() const = 0; - - virtual void applySentMessage(const MTPDmessage &data); - virtual void applySentMessage( + const MTPMessageMedia *media); + void applySentMessage(const MTPDmessage &data); + void applySentMessage( const QString &text, const MTPDupdateShortSentMessage &data, bool wasAlready); + void updateReactions(const MTPMessageReactions *reactions); + + void applyEditionToHistoryCleared(); + void updateReplyMarkup(HistoryMessageMarkupData &&markup); + void contributeToSlowmode(TimeId realDate = 0); + + void addToUnreadThings(HistoryUnreadThings::AddType type); + void destroyHistoryEntry(); + [[nodiscard]] Storage::SharedMediaTypesMask sharedMediaTypes() const; void indexAsNewItem(); - [[nodiscard]] virtual QString notificationHeader() const { - return QString(); - } - [[nodiscard]] virtual TextWithEntities notificationText() const; + [[nodiscard]] QString notificationHeader() const; + [[nodiscard]] TextWithEntities notificationText() const; using ToPreviewOptions = HistoryView::ToPreviewOptions; using ItemPreview = HistoryView::ItemPreview; // Returns text with link-start and link-end commands for service-color highlighting. // Example: "[link1-start]You:[link1-end] [link1-start]Photo,[link1-end] caption text" - [[nodiscard]] virtual ItemPreview toPreview( - ToPreviewOptions options) const; - [[nodiscard]] virtual TextWithEntities inReplyText() const; - [[nodiscard]] virtual TextWithEntities originalText() const { - return TextWithEntities(); - } - [[nodiscard]] virtual auto originalTextWithLocalEntities() const - -> TextWithEntities { - return TextWithEntities(); - } - [[nodiscard]] virtual auto customTextLinks() const - -> const std::vector &; - [[nodiscard]] virtual TextForMimeData clipboardText() const { - return TextForMimeData(); - } + [[nodiscard]] ItemPreview toPreview(ToPreviewOptions options) const; + [[nodiscard]] TextWithEntities inReplyText() const; + [[nodiscard]] TextWithEntities originalText() const; + [[nodiscard]] TextWithEntities originalTextWithLocalEntities() const; + [[nodiscard]] const std::vector &customTextLinks() const; + [[nodiscard]] TextForMimeData clipboardText() const; - virtual bool changeViewsCount(int count) { - return false; - } - virtual void setForwardsCount(int count) { - } - virtual void setReplies(HistoryMessageRepliesData &&data) { - } - virtual void clearReplies() { - } - virtual void changeRepliesCount(int delta, PeerId replier) { - } - virtual void setReplyFields( + bool changeViewsCount(int count); + void setForwardsCount(int count); + void setReplies(HistoryMessageRepliesData &&data); + void clearReplies(); + void changeRepliesCount(int delta, PeerId replier); + void setReplyFields( MsgId replyTo, MsgId replyToTop, - bool isForumPost) = 0; - virtual void setPostAuthor(const QString &author) { - } - virtual void setRealId(MsgId newId); - virtual void incrementReplyToTopCounter() { - } + bool isForumPost); + void setPostAuthor(const QString &author); + void setRealId(MsgId newId); + void incrementReplyToTopCounter(); [[nodiscard]] bool emptyText() const { return _text.empty(); @@ -346,9 +383,9 @@ public: [[nodiscard]] bool canStopPoll() const; [[nodiscard]] bool forbidsForward() const; [[nodiscard]] bool forbidsSaving() const; - [[nodiscard]] virtual bool allowsSendNow() const; - [[nodiscard]] virtual bool allowsForward() const; - [[nodiscard]] virtual bool allowsEdit(TimeId now) const; + [[nodiscard]] bool allowsSendNow() const; + [[nodiscard]] bool allowsForward() const; + [[nodiscard]] bool allowsEdit(TimeId now) const; [[nodiscard]] bool canDelete() const; [[nodiscard]] bool canDeleteForEveryone(TimeId now) const; [[nodiscard]] bool suggestReport() const; @@ -364,7 +401,6 @@ public: void toggleReaction( const Data::ReactionId &reaction, ReactionSource source); - void updateReactions(const MTPMessageReactions *reactions); void updateReactionsUnknown(); [[nodiscard]] auto reactions() const -> const std::vector &; @@ -391,12 +427,11 @@ public: return _media.get(); } [[nodiscard]] bool computeDropForwardedInfo() const; - virtual void setText(const TextWithEntities &textWithEntities) { - } + void setText(const TextWithEntities &textWithEntities); - [[nodiscard]] virtual MsgId replyToId() const = 0; - [[nodiscard]] virtual MsgId replyToTop() const = 0; - [[nodiscard]] virtual MsgId topicRootId() const = 0; + [[nodiscard]] MsgId replyToId() const; + [[nodiscard]] MsgId replyToTop() const; + [[nodiscard]] MsgId topicRootId() const; [[nodiscard]] bool inThread(MsgId rootId) const; [[nodiscard]] not_null author() const; @@ -426,9 +461,9 @@ public: [[nodiscard]] HistoryItem *lookupDiscussionPostOriginal() const; [[nodiscard]] PeerData *displayFrom() const; - [[nodiscard]] virtual std::unique_ptr createView( + [[nodiscard]] std::unique_ptr createView( not_null delegate, - HistoryView::Element *replacing = nullptr) = 0; + HistoryView::Element *replacing = nullptr); void updateDate(TimeId newDate); [[nodiscard]] bool canUpdateDate() const; @@ -438,11 +473,16 @@ public: return _ttlDestroyAt; } - virtual ~HistoryItem(); - MsgId id; -protected: +private: + struct CreateConfig; + + struct SavedMediaData { + TextWithEntities text; + std::unique_ptr media; + }; + HistoryItem( not_null history, MsgId id, @@ -450,63 +490,113 @@ protected: TimeId date, PeerId from); - virtual void markMediaAsReadHook() { + void createComponentsHelper( + MessageFlags flags, + MsgId replyTo, + UserId viaBotId, + const QString &postAuthor, + HistoryMessageMarkupData &&markup); + void createComponents(CreateConfig &&config); + void setupForwardedComponent(const CreateConfig &config); + + [[nodiscard]] bool generateLocalEntitiesByReply() const; + [[nodiscard]] TextWithEntities withLocalEntities( + const TextWithEntities &textWithEntities) const; + void setTextValue(TextWithEntities text); + [[nodiscard]] bool isTooOldForEdit(TimeId now) const; + [[nodiscard]] bool isLegacyMessage() const { + return _flags & MessageFlag::Legacy; } - void applyServiceDateEdition(const MTPDmessageService &data); + [[nodiscard]] bool checkCommentsLinkedChat(ChannelId id) const; + + void setReplyMarkup(HistoryMessageMarkupData &&markup); + + void changeReplyToTopCounter( + not_null reply, + int delta); + void refreshRepliesText( + not_null views, + bool forceResize = false); + + [[nodiscard]] bool checkRepliesPts( + const HistoryMessageRepliesData &data) const; + + [[nodiscard]] HistoryServiceDependentData *GetServiceDependentData(); + [[nodiscard]] auto GetServiceDependentData() const + -> const HistoryServiceDependentData *; + void updateDependentServiceText(); + bool updateServiceDependent(bool force = false); + void setServiceText(PreparedServiceText &&prepared); + void finishEdition(int oldKeyboardTop); void finishEditionToEmpty(); + void clearDependencyMessage(); + void setupChatThemeChange(); + void setupTTLChange(); + + void setSelfDestruct(HistorySelfDestructType type, int ttlSeconds); + + TextWithEntities fromLinkText() const; + ClickHandlerPtr fromLink() const; + + void setGroupId(MessageGroupId groupId); + + static void FillForwardedInfo( + CreateConfig &config, + const MTPDmessageFwdHeader &data); + void createComponents(const MTPDmessage &data); + void setMedia(const MTPMessageMedia &media); + void applyServiceDateEdition(const MTPDmessageService &data); void setReactions(const MTPMessageReactions *reactions); [[nodiscard]] bool changeReactions(const MTPMessageReactions *reactions); + void setServiceMessageByAction(const MTPmessageAction &action); + void applyAction(const MTPMessageAction &action); + void refreshMedia(const MTPMessageMedia *media); + void refreshSentMedia(const MTPMessageMedia *media); + void createServiceFromMtp(const MTPDmessage &message); + void createServiceFromMtp(const MTPDmessageService &message); + void applyTTL(const MTPDmessage &data); + void applyTTL(const MTPDmessageService &data); + + void applyTTL(TimeId destroyAt); + + // For an invoice button we replace the button text with a "Receipt" key. + // It should show the receipt for the payed invoice. Still let mobile apps do that. + void replaceBuyWithReceiptInMarkup(); + + void setSponsoredFrom(const Data::SponsoredFrom &from); + + [[nodiscard]] PreparedServiceText preparePinnedText(); + [[nodiscard]] PreparedServiceText prepareGameScoreText(); + [[nodiscard]] PreparedServiceText preparePaymentSentText(); + [[nodiscard]] PreparedServiceText prepareInvitedToCallText( + const std::vector> &users, + CallId linkCallId); + [[nodiscard]] PreparedServiceText prepareCallScheduledText( + TimeId scheduleDate); const not_null _history; const not_null _from; MessageFlags _flags = 0; - void setGroupId(MessageGroupId groupId); - - void applyTTL(const MTPDmessage &data); - void applyTTL(const MTPDmessageService &data); - void applyTTL(TimeId destroyAt); - TextWithEntities _text; - struct SavedMediaData { - TextWithEntities text; - std::unique_ptr media; - }; - std::unique_ptr _savedLocalEditMediaData; std::unique_ptr _media; std::unique_ptr _reactions; crl::time _reactionsLastRefreshed = 0; -private: TimeId _date = 0; TimeId _ttlDestroyAt = 0; HistoryView::Element *_mainView = nullptr; - friend class HistoryView::Element; - MessageGroupId _groupId = MessageGroupId(); + friend class HistoryView::Element; + friend class HistoryView::Message; + friend class HistoryView::Service; + friend class HistoryView::ServiceMessagePainter; + }; - -[[nodiscard]] Main::Session *SessionByUniqueId(uint64 sessionUniqueId); -[[nodiscard]] HistoryItem *MessageByGlobalId(GlobalMsgId globalId); - -[[nodiscard]] QDateTime ItemDateTime(not_null item); -[[nodiscard]] QString ItemDateText( - not_null item, - bool isUntilOnline); -[[nodiscard]] bool IsItemScheduledUntilOnline( - not_null item); - -ClickHandlerPtr goToMessageClickHandler( - not_null peer, - MsgId msgId, - FullMsgId returnToId = FullMsgId()); -ClickHandlerPtr goToMessageClickHandler( - not_null item, - FullMsgId returnToId = FullMsgId()); diff --git a/Telegram/SourceFiles/history/history_item_components.cpp b/Telegram/SourceFiles/history/history_item_components.cpp index c9832365d..532a04359 100644 --- a/Telegram/SourceFiles/history/history_item_components.cpp +++ b/Telegram/SourceFiles/history/history_item_components.cpp @@ -18,7 +18,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/chat/chat_theme.h" #include "ui/painter.h" #include "history/history.h" -#include "history/history_message.h" +#include "history/history_item.h" +#include "history/history_item_helpers.h" #include "history/view/history_view_message.h" // FromNameFg. #include "history/view/history_view_service_message.h" #include "history/view/media/history_view_document.h" @@ -244,7 +245,7 @@ void HistoryMessageForwarded::create(const HistoryMessageVia *via) const { text.setMarkedText(st::fwdTextStyle, phrase); text.setLink(1, fromChannel - ? goToMessageClickHandler(originalSender, originalId) + ? JumpToMessageClickHandler(originalSender, originalId) : originalSender ? originalSender->openLink() : HiddenSenderInfo::ForwardClickHandler()); @@ -254,7 +255,7 @@ void HistoryMessageForwarded::create(const HistoryMessageVia *via) const { } bool HistoryMessageReply::updateData( - not_null holder, + not_null holder, bool force) { const auto guard = gsl::finally([&] { refreshReplyToMedia(); }); if (!force) { @@ -322,13 +323,13 @@ bool HistoryMessageReply::updateData( } void HistoryMessageReply::setReplyToLinkFrom( - not_null holder) { + not_null holder) { replyToLnk = replyToMsg - ? goToMessageClickHandler(replyToMsg.get(), holder->fullId()) + ? JumpToMessageClickHandler(replyToMsg.get(), holder->fullId()) : nullptr; } -void HistoryMessageReply::clearData(not_null holder) { +void HistoryMessageReply::clearData(not_null holder) { replyToVia = nullptr; if (replyToMsg) { holder->history()->owner().unregisterDependentMessage( @@ -341,7 +342,7 @@ void HistoryMessageReply::clearData(not_null holder) { } PeerData *HistoryMessageReply::replyToFrom( - not_null holder) const { + not_null holder) const { if (!replyToMsg) { return nullptr; } else if (holder->Has()) { @@ -356,7 +357,7 @@ PeerData *HistoryMessageReply::replyToFrom( } QString HistoryMessageReply::replyToFromName( - not_null holder) const { + not_null holder) const { if (!replyToMsg) { return QString(); } else if (holder->Has()) { @@ -381,7 +382,7 @@ QString HistoryMessageReply::replyToFromName( } bool HistoryMessageReply::isNameUpdated( - not_null holder) const { + not_null holder) const { if (const auto from = replyToFrom(holder)) { if (replyToVersion < from->nameVersion()) { updateName(holder); @@ -392,7 +393,7 @@ bool HistoryMessageReply::isNameUpdated( } void HistoryMessageReply::updateName( - not_null holder) const { + not_null holder) const { if (const auto name = replyToFromName(holder); !name.isEmpty()) { replyToName.setText(st::fwdTextStyle, name, Ui::NameTextOptions()); if (const auto from = replyToFrom(holder)) { @@ -423,7 +424,7 @@ void HistoryMessageReply::resize(int width) const { } void HistoryMessageReply::itemRemoved( - HistoryMessage *holder, + HistoryItem *holder, HistoryItem *removed) { if (replyToMsg.get() == removed) { clearData(holder); diff --git a/Telegram/SourceFiles/history/history_item_components.h b/Telegram/SourceFiles/history/history_item_components.h index 2b04d7784..1626a90cb 100644 --- a/Telegram/SourceFiles/history/history_item_components.h +++ b/Telegram/SourceFiles/history/history_item_components.h @@ -195,20 +195,19 @@ struct HistoryMessageReply Expects(replyToVia == nullptr); } - bool updateData(not_null holder, bool force = false); + bool updateData(not_null holder, bool force = false); // Must be called before destructor. - void clearData(not_null holder); + void clearData(not_null holder); - [[nodiscard]] PeerData *replyToFrom( - not_null holder) const; + [[nodiscard]] PeerData *replyToFrom(not_null holder) const; [[nodiscard]] QString replyToFromName( - not_null holder) const; + not_null holder) const; [[nodiscard]] QString replyToFromName(not_null peer) const; - [[nodiscard]] bool isNameUpdated(not_null holder) const; - void updateName(not_null holder) const; + [[nodiscard]] bool isNameUpdated(not_null holder) const; + void updateName(not_null holder) const; void resize(int width) const; - void itemRemoved(HistoryMessage *holder, HistoryItem *removed); + void itemRemoved(HistoryItem *holder, HistoryItem *removed); void paint( Painter &p, @@ -234,8 +233,7 @@ struct HistoryMessageReply [[nodiscard]] ClickHandlerPtr replyToLink() const { return replyToLnk; } - void setReplyToLinkFrom( - not_null holder); + void setReplyToLinkFrom(not_null holder); void refreshReplyToMedia(); @@ -452,7 +450,7 @@ private: // Special type of Component for the channel actions log. struct HistoryMessageLogEntryOriginal - : public RuntimeComponent { +: public RuntimeComponent { HistoryMessageLogEntryOriginal(); HistoryMessageLogEntryOriginal(HistoryMessageLogEntryOriginal &&other); HistoryMessageLogEntryOriginal &operator=(HistoryMessageLogEntryOriginal &&other); @@ -462,6 +460,94 @@ struct HistoryMessageLogEntryOriginal }; +struct HistoryServiceData +: public RuntimeComponent { + std::vector textLinks; +}; + +struct HistoryServiceDependentData { + PeerId peerId = 0; + HistoryItem *msg = nullptr; + ClickHandlerPtr lnk; + MsgId msgId = 0; + MsgId topId = 0; + bool topicPost = false; +}; + +struct HistoryServicePinned +: public RuntimeComponent +, public HistoryServiceDependentData { +}; + +struct HistoryServiceTopicInfo +: public RuntimeComponent +, public HistoryServiceDependentData { + QString title; + DocumentId iconId = 0; + bool closed = false; + bool reopened = false; + bool reiconed = false; + bool renamed = false; + bool hidden = false; + bool unhidden = false; + + [[nodiscard]] bool created() const { + return !closed + && !reopened + && !reiconed + && !renamed + && !hidden + && !unhidden; + } +}; + +struct HistoryServiceGameScore +: public RuntimeComponent +, public HistoryServiceDependentData { + int score = 0; +}; + +struct HistoryServicePayment +: public RuntimeComponent +, public HistoryServiceDependentData { + QString slug; + QString amount; + ClickHandlerPtr invoiceLink; + bool recurringInit = false; + bool recurringUsed = false; +}; + +enum class HistorySelfDestructType { + Photo, + Video, +}; + +struct HistoryServiceSelfDestruct +: public RuntimeComponent { + using Type = HistorySelfDestructType; + + Type type = Type::Photo; + crl::time timeToLive = 0; + crl::time destructAt = 0; +}; + +struct HistoryServiceOngoingCall +: public RuntimeComponent { + CallId id = 0; + ClickHandlerPtr link; + rpl::lifetime lifetime; +}; + +struct HistoryServiceChatThemeChange +: public RuntimeComponent { + ClickHandlerPtr link; +}; + +struct HistoryServiceTTLChange +: public RuntimeComponent { + ClickHandlerPtr link; +}; + class FileClickHandler; struct HistoryDocumentThumbed : public RuntimeComponent { std::shared_ptr linksavel; diff --git a/Telegram/SourceFiles/history/history_item_helpers.cpp b/Telegram/SourceFiles/history/history_item_helpers.cpp new file mode 100644 index 000000000..dd569d5c8 --- /dev/null +++ b/Telegram/SourceFiles/history/history_item_helpers.cpp @@ -0,0 +1,656 @@ +/* +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 "history/history_item_helpers.h" + +#include "calls/calls_instance.h" +#include "data/notify/data_notify_settings.h" +#include "data/data_chat_participant_status.h" +#include "data/data_channel.h" +#include "data/data_chat.h" +#include "data/data_changes.h" +#include "data/data_group_call.h" +#include "data/data_forum.h" +#include "data/data_forum_topic.h" +#include "data/data_media_types.h" +#include "data/data_message_reactions.h" +#include "data/data_scheduled_messages.h" +#include "data/data_session.h" +#include "data/data_user.h" +#include "history/history.h" +#include "history/history_item.h" +#include "history/history_item_components.h" +#include "main/main_account.h" +#include "main/main_domain.h" +#include "main/main_session.h" +#include "main/main_session_settings.h" +#include "window/window_controller.h" +#include "window/window_session_controller.h" +#include "apiwrap.h" +#include "base/unixtime.h" +#include "core/application.h" +#include "ui/text/format_values.h" +#include "ui/text/text_utilities.h" +#include "ui/text/text_entity.h" +#include "ui/item_text_options.h" +#include "lang/lang_keys.h" + +namespace { + +[[nodiscard]] bool HasInlineItems(const HistoryItemsList &items) { + for (const auto &item : items) { + if (item->viaBot()) { + return true; + } + } + return false; +} + +bool PeerCallKnown(not_null peer) { + if (peer->groupCall() != nullptr) { + return true; + } else if (const auto chat = peer->asChat()) { + return !(chat->flags() & ChatDataFlag::CallActive); + } else if (const auto channel = peer->asChannel()) { + return !(channel->flags() & ChannelDataFlag::CallActive); + } + return true; +} + +} // namespace + +QString GetErrorTextForSending( + not_null peer, + SendingErrorRequest request) { + const auto forum = request.topicRootId ? peer->forum() : nullptr; + const auto topic = forum + ? forum->topicFor(request.topicRootId) + : nullptr; + if (!(topic ? topic->canWrite() : peer->canWrite())) { + return tr::lng_forward_cant(tr::now); + } + + if (request.forward) { + for (const auto &item : *request.forward) { + if (const auto media = item->media()) { + const auto error = media->errorTextForForward(peer); + if (!error.isEmpty() && error != u"skip"_q) { + return error; + } + } + } + } + const auto error = Data::RestrictionError( + peer, + ChatRestriction::SendInline); + if (error && request.forward && HasInlineItems(*request.forward)) { + return *error; + } + + if (peer->slowmodeApplied()) { + const auto hasText = (request.text && !request.text->empty()); + const auto count = (hasText ? 1 : 0) + + (request.forward ? int(request.forward->size()) : 0); + if (const auto history = peer->owner().historyLoaded(peer)) { + if (!request.ignoreSlowmodeCountdown + && (history->latestSendingMessage() != nullptr) + && (count > 0)) { + return tr::lng_slowmode_no_many(tr::now); + } + } + if (request.text && request.text->text.size() > MaxMessageSize) { + return tr::lng_slowmode_too_long(tr::now); + } else if (hasText && count > 1) { + return tr::lng_slowmode_no_many(tr::now); + } else if (count > 1) { + const auto albumForward = [&] { + const auto first = request.forward->front(); + if (const auto groupId = first->groupId()) { + for (const auto &item : *request.forward) { + if (item->groupId() != groupId) { + return false; + } + } + return true; + } + return false; + }(); + if (!albumForward) { + return tr::lng_slowmode_no_many(tr::now); + } + } + } + if (const auto left = peer->slowmodeSecondsLeft()) { + if (!request.ignoreSlowmodeCountdown) { + return tr::lng_slowmode_enabled( + tr::now, + lt_left, + Ui::FormatDurationWordsSlowmode(left)); + } + } + + return QString(); +} + +QString GetErrorTextForSending( + not_null thread, + SendingErrorRequest request) { + request.topicRootId = thread->topicRootId(); + return GetErrorTextForSending(thread->peer(), std::move(request)); +} + +void RequestDependentMessageData( + not_null item, + PeerId peerId, + MsgId msgId) { + if (!IsServerMsgId(msgId)) { + return; + } + const auto fullId = item->fullId(); + const auto history = item->history(); + const auto session = &history->session(); + const auto done = [=] { + if (const auto item = session->data().message(fullId)) { + item->updateDependencyItem(); + } + }; + history->session().api().requestMessageData( + (peerId ? history->owner().peer(peerId) : history->peer), + msgId, + done); +} + +MessageFlags NewMessageFlags(not_null peer) { + return MessageFlag::BeingSent + | (peer->isSelf() ? MessageFlag() : MessageFlag::Outgoing); +} + +bool ShouldSendSilent( + not_null peer, + const Api::SendOptions &options) { + return options.silent + || (peer->isBroadcast() + && peer->owner().notifySettings().silentPosts(peer)) + || (peer->session().supportMode() + && peer->session().settings().supportAllSilent()); +} + +HistoryItem *LookupReplyTo(not_null history, MsgId replyToId) { + const auto &owner = history->owner(); + return owner.message(history->peer, replyToId); +} + +MsgId LookupReplyToTop(HistoryItem *replyTo) { + return replyTo ? replyTo->replyToTop() : 0; +} + +bool LookupReplyIsTopicPost(HistoryItem *replyTo) { + return replyTo + && (replyTo->topicRootId() != Data::ForumTopic::kGeneralId); +} + +TextWithEntities DropCustomEmoji(TextWithEntities text) { + text.entities.erase( + ranges::remove( + text.entities, + EntityType::CustomEmoji, + &EntityInText::type), + text.entities.end()); + return text; +} + +Main::Session *SessionByUniqueId(uint64 sessionUniqueId) { + if (!sessionUniqueId) { + return nullptr; + } + for (const auto &[index, account] : Core::App().domain().accounts()) { + if (const auto session = account->maybeSession()) { + if (session->uniqueId() == sessionUniqueId) { + return session; + } + } + } + return nullptr; +} + +HistoryItem *MessageByGlobalId(GlobalMsgId globalId) { + const auto sessionId = globalId.itemId ? globalId.sessionUniqueId : 0; + if (const auto session = SessionByUniqueId(sessionId)) { + return session->data().message(globalId.itemId); + } + return nullptr; +} + +QDateTime ItemDateTime(not_null item) { + return base::unixtime::parse(item->date()); +} + +QString ItemDateText(not_null item, bool isUntilOnline) { + const auto dateText = langDayOfMonthFull(ItemDateTime(item).date()); + return !item->isScheduled() + ? dateText + : isUntilOnline + ? tr::lng_scheduled_date_until_online(tr::now) + : tr::lng_scheduled_date(tr::now, lt_date, dateText); +} + +bool IsItemScheduledUntilOnline(not_null item) { + return item->isScheduled() + && (item->date() == + Data::ScheduledMessages::kScheduledUntilOnlineTimestamp); +} + +ClickHandlerPtr JumpToMessageClickHandler( + not_null item, + FullMsgId returnToId) { + return JumpToMessageClickHandler( + item->history()->peer, + item->id, + returnToId); +} + +ClickHandlerPtr JumpToMessageClickHandler( + not_null peer, + MsgId msgId, + FullMsgId returnToId) { + return std::make_shared([=] { + const auto separate = Core::App().separateWindowForPeer(peer); + const auto controller = separate + ? separate->sessionController() + : peer->session().tryResolveWindow(); + if (controller) { + auto params = Window::SectionShow{ + Window::SectionShow::Way::Forward + }; + params.origin = Window::SectionShow::OriginMessage{ + returnToId + }; + if (const auto item = peer->owner().message(peer, msgId)) { + controller->showMessage(item, params); + } else { + controller->showPeerHistory(peer, params, msgId); + } + } + }); +} + +MessageFlags FlagsFromMTP( + MsgId id, + MTPDmessage::Flags flags, + MessageFlags localFlags) { + using Flag = MessageFlag; + using MTP = MTPDmessage::Flag; + return localFlags + | (IsServerMsgId(id) ? Flag::HistoryEntry : Flag()) + | ((flags & MTP::f_out) ? Flag::Outgoing : Flag()) + | ((flags & MTP::f_mentioned) ? Flag::MentionsMe : Flag()) + | ((flags & MTP::f_media_unread) ? Flag::MediaIsUnread : Flag()) + | ((flags & MTP::f_silent) ? Flag::Silent : Flag()) + | ((flags & MTP::f_post) ? Flag::Post : Flag()) + | ((flags & MTP::f_legacy) ? Flag::Legacy : Flag()) + | ((flags & MTP::f_edit_hide) ? Flag::HideEdited : Flag()) + | ((flags & MTP::f_pinned) ? Flag::Pinned : Flag()) + | ((flags & MTP::f_from_id) ? Flag::HasFromId : Flag()) + | ((flags & MTP::f_reply_to) ? Flag::HasReplyInfo : Flag()) + | ((flags & MTP::f_reply_markup) ? Flag::HasReplyMarkup : Flag()) + | ((flags & MTP::f_from_scheduled) ? Flag::IsOrWasScheduled : Flag()) + | ((flags & MTP::f_views) ? Flag::HasViews : Flag()) + | ((flags & MTP::f_noforwards) ? Flag::NoForwards : Flag()); +} + +MessageFlags FlagsFromMTP( + MsgId id, + MTPDmessageService::Flags flags, + MessageFlags localFlags) { + using Flag = MessageFlag; + using MTP = MTPDmessageService::Flag; + return localFlags + | (IsServerMsgId(id) ? Flag::HistoryEntry : Flag()) + | ((flags & MTP::f_out) ? Flag::Outgoing : Flag()) + | ((flags & MTP::f_mentioned) ? Flag::MentionsMe : Flag()) + | ((flags & MTP::f_media_unread) ? Flag::MediaIsUnread : Flag()) + | ((flags & MTP::f_silent) ? Flag::Silent : Flag()) + | ((flags & MTP::f_post) ? Flag::Post : Flag()) + | ((flags & MTP::f_legacy) ? Flag::Legacy : Flag()) + | ((flags & MTP::f_from_id) ? Flag::HasFromId : Flag()) + | ((flags & MTP::f_reply_to) ? Flag::HasReplyInfo : Flag()); +} + +MTPMessageReplyHeader NewMessageReplyHeader(const Api::SendAction &action) { + if (const auto id = action.replyTo) { + const auto to = LookupReplyTo(action.history, id); + if (const auto replyToTop = LookupReplyToTop(to)) { + using Flag = MTPDmessageReplyHeader::Flag; + return MTP_messageReplyHeader( + MTP_flags(Flag::f_reply_to_top_id + | (LookupReplyIsTopicPost(to) + ? Flag::f_forum_topic + : Flag(0))), + MTP_int(id), + MTPPeer(), + MTP_int(replyToTop)); + } + return MTP_messageReplyHeader( + MTP_flags(0), + MTP_int(id), + MTPPeer(), + MTPint()); + } + return MTPMessageReplyHeader(); +} + +MediaCheckResult CheckMessageMedia(const MTPMessageMedia &media) { + using Result = MediaCheckResult; + return media.match([](const MTPDmessageMediaEmpty &) { + return Result::Good; + }, [](const MTPDmessageMediaContact &) { + return Result::Good; + }, [](const MTPDmessageMediaGeo &data) { + return data.vgeo().match([](const MTPDgeoPoint &) { + return Result::Good; + }, [](const MTPDgeoPointEmpty &) { + return Result::Empty; + }); + }, [](const MTPDmessageMediaVenue &data) { + return data.vgeo().match([](const MTPDgeoPoint &) { + return Result::Good; + }, [](const MTPDgeoPointEmpty &) { + return Result::Empty; + }); + }, [](const MTPDmessageMediaGeoLive &data) { + return data.vgeo().match([](const MTPDgeoPoint &) { + return Result::Good; + }, [](const MTPDgeoPointEmpty &) { + return Result::Empty; + }); + }, [](const MTPDmessageMediaPhoto &data) { + const auto photo = data.vphoto(); + if (data.vttl_seconds()) { + return Result::HasTimeToLive; + } else if (!photo) { + return Result::Empty; + } + return photo->match([](const MTPDphoto &) { + return Result::Good; + }, [](const MTPDphotoEmpty &) { + return Result::Empty; + }); + }, [](const MTPDmessageMediaDocument &data) { + const auto document = data.vdocument(); + if (data.vttl_seconds()) { + return Result::HasTimeToLive; + } else if (!document) { + return Result::Empty; + } + return document->match([](const MTPDdocument &) { + return Result::Good; + }, [](const MTPDdocumentEmpty &) { + return Result::Empty; + }); + }, [](const MTPDmessageMediaWebPage &data) { + return data.vwebpage().match([](const MTPDwebPage &) { + return Result::Good; + }, [](const MTPDwebPageEmpty &) { + return Result::Good; + }, [](const MTPDwebPagePending &) { + return Result::Good; + }, [](const MTPDwebPageNotModified &) { + return Result::Unsupported; + }); + }, [](const MTPDmessageMediaGame &data) { + return data.vgame().match([](const MTPDgame &) { + return Result::Good; + }); + }, [](const MTPDmessageMediaInvoice &) { + return Result::Good; + }, [](const MTPDmessageMediaPoll &) { + return Result::Good; + }, [](const MTPDmessageMediaDice &) { + return Result::Good; + }, [](const MTPDmessageMediaUnsupported &) { + return Result::Unsupported; + }); +} + +[[nodiscard]] CallId CallIdFromInput(const MTPInputGroupCall &data) { + return data.match([&](const MTPDinputGroupCall &data) { + return data.vid().v; + }); +} + +std::vector> ParseInvitedToCallUsers( + not_null item, + const QVector &users) { + auto &owner = item->history()->owner(); + return ranges::views::all( + users + ) | ranges::views::transform([&](const MTPlong &id) { + return owner.user(id.v); + }) | ranges::to_vector; +} + +PreparedServiceText GenerateJoinedText( + not_null history, + not_null inviter, + bool viaRequest) { + if (inviter->id != history->session().userPeerId()) { + auto result = PreparedServiceText(); + result.links.push_back(inviter->createOpenLink()); + result.text = (history->peer->isMegagroup() + ? tr::lng_action_add_you_group + : tr::lng_action_add_you)( + tr::now, + lt_from, + Ui::Text::Link(inviter->name(), QString()), + Ui::Text::WithEntities); + return result; + } else if (history->peer->isMegagroup()) { + if (viaRequest) { + return { tr::lng_action_you_joined_by_request( + tr::now, + Ui::Text::WithEntities) }; + } + auto self = history->session().user(); + auto result = PreparedServiceText(); + result.links.push_back(self->createOpenLink()); + result.text = tr::lng_action_user_joined( + tr::now, + lt_from, + Ui::Text::Link(self->name(), QString()), + Ui::Text::WithEntities); + return result; + } + return { viaRequest + ? tr::lng_action_you_joined_by_request_channel( + tr::now, + Ui::Text::WithEntities) + : tr::lng_action_you_joined(tr::now, Ui::Text::WithEntities) }; +} + +not_null GenerateJoinedMessage( + not_null history, + TimeId inviteDate, + not_null inviter, + bool viaRequest) { + return history->makeMessage( + history->owner().nextLocalMessageId(), + MessageFlag::Local, + inviteDate, + GenerateJoinedText(history, inviter, viaRequest)); +} + +std::optional PeerHasThisCall( + not_null peer, + CallId id) { + const auto call = peer->groupCall(); + return call + ? std::make_optional(call->id() == id) + : PeerCallKnown(peer) + ? std::make_optional(false) + : std::nullopt; +} +[[nodiscard]] rpl::producer PeerHasThisCallValue( + not_null peer, + CallId id) { + return peer->session().changes().peerFlagsValue( + peer, + Data::PeerUpdate::Flag::GroupCall + ) | rpl::filter([=] { + return PeerCallKnown(peer); + }) | rpl::map([=] { + const auto call = peer->groupCall(); + return (call && call->id() == id); + }) | rpl::distinct_until_changed( + ) | rpl::take_while([=](bool hasThisCall) { + return hasThisCall; + }) | rpl::then( + rpl::single(false) + ); +} + +[[nodiscard]] ClickHandlerPtr GroupCallClickHandler( + not_null peer, + CallId callId) { + return std::make_shared([=] { + const auto call = peer->groupCall(); + if (call && call->id() == callId) { + const auto &windows = peer->session().windows(); + if (windows.empty()) { + Core::App().domain().activate(&peer->session().account()); + if (windows.empty()) { + return; + } + } + windows.front()->startOrJoinGroupCall(peer, {}); + } + }); +} + +[[nodiscard]] MessageFlags FinalizeMessageFlags(MessageFlags flags) { + if (!(flags & MessageFlag::FakeHistoryItem) + && !(flags & MessageFlag::IsOrWasScheduled) + && !(flags & MessageFlag::AdminLogEntry)) { + flags |= MessageFlag::HistoryEntry; + } + return flags; +} + +using OnStackUsers = std::array; +[[nodiscard]] OnStackUsers LookupRecentUnreadReactedUsers( + not_null item) { + auto result = OnStackUsers(); + auto index = 0; + for (const auto &[emoji, reactions] : item->recentReactions()) { + for (const auto &reaction : reactions) { + if (!reaction.unread) { + continue; + } + if (const auto user = reaction.peer->asUser()) { + result[index++] = user; + if (index == result.size()) { + return result; + } + } + } + } + return result; +} + +void CheckReactionNotificationSchedule( + not_null item, + const OnStackUsers &wasUsers) { + // Call to addToUnreadThings may have read the reaction already. + if (!item->hasUnreadReaction()) { + return; + } + for (const auto &[emoji, reactions] : item->recentReactions()) { + for (const auto &reaction : reactions) { + if (!reaction.unread) { + continue; + } + const auto user = reaction.peer->asUser(); + if (!user + || !user->isContact() + || ranges::contains(wasUsers, user)) { + continue; + } + using Status = PeerData::BlockStatus; + if (user->blockStatus() == Status::Unknown) { + user->updateFull(); + } + const auto notification = Data::ItemNotification{ + .item = item, + .reactionSender = user, + .type = Data::ItemNotificationType::Reaction, + }; + item->notificationThread()->pushNotification(notification); + Core::App().notifications().schedule(notification); + return; + } + } +} + +[[nodiscard]] MessageFlags NewForwardedFlags( + not_null peer, + PeerId from, + not_null fwd) { + auto result = NewMessageFlags(peer); + if (from) { + result |= MessageFlag::HasFromId; + } + if (const auto media = fwd->media()) { + if ((!peer->isChannel() || peer->isMegagroup()) + && media->forwardedBecomesUnread()) { + result |= MessageFlag::MediaIsUnread; + } + } + if (fwd->hasViews()) { + result |= MessageFlag::HasViews; + } + return result; +} + +[[nodiscard]] bool CopyMarkupToForward(not_null item) { + auto mediaOriginal = item->media(); + if (mediaOriginal && mediaOriginal->game()) { + // Copy inline keyboard when forwarding messages with a game. + return true; + } + const auto markup = item->inlineReplyMarkup(); + if (!markup) { + return false; + } + using Type = HistoryMessageMarkupButton::Type; + for (const auto &row : markup->data.rows) { + for (const auto &button : row) { + const auto switchInline = (button.type == Type::SwitchInline) + || (button.type == Type::SwitchInlineSame); + const auto url = (button.type == Type::Url) + || (button.type == Type::Auth); + if ((!switchInline || !item->viaBot()) && !url) { + return false; + } + } + } + return true; +} + +[[nodiscard]] TextWithEntities EnsureNonEmpty( + const TextWithEntities &text) { + return !text.text.isEmpty() ? text : TextWithEntities{ u":-("_q }; +} + +[[nodiscard]] TextWithEntities UnsupportedMessageText() { + const auto siteLink = u"https://desktop.telegram.org"_q; + auto result = TextWithEntities{ + tr::lng_message_unsupported(tr::now, lt_link, siteLink) + }; + TextUtilities::ParseEntities(result, Ui::ItemTextNoMonoOptions().flags); + result.entities.push_front( + EntityInText(EntityType::Italic, 0, result.text.size())); + return result; +} diff --git a/Telegram/SourceFiles/history/history_item_helpers.h b/Telegram/SourceFiles/history/history_item_helpers.h new file mode 100644 index 000000000..46b772517 --- /dev/null +++ b/Telegram/SourceFiles/history/history_item_helpers.h @@ -0,0 +1,133 @@ +/* +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 +*/ +#pragma once + +class History; + +namespace Api { +struct SendOptions; +struct SendAction; +} // namespace Api + +namespace Data { +class Thread; +} // namespace Data + +namespace Main { +class Session; +} // namespace Main + +struct PreparedServiceText { + TextWithEntities text; + std::vector links; +}; + +[[nodiscard]] MessageFlags FlagsFromMTP( + MsgId id, + MTPDmessage::Flags flags, + MessageFlags localFlags); +[[nodiscard]] MessageFlags FlagsFromMTP( + MsgId id, + MTPDmessageService::Flags flags, + MessageFlags localFlags); +[[nodiscard]] MTPMessageReplyHeader NewMessageReplyHeader( + const Api::SendAction &action); + +enum class MediaCheckResult { + Good, + Unsupported, + Empty, + HasTimeToLive, +}; +[[nodiscard]] MediaCheckResult CheckMessageMedia( + const MTPMessageMedia &media); +[[nodiscard]] CallId CallIdFromInput(const MTPInputGroupCall &data); + +[[nodiscard]] std::vector> ParseInvitedToCallUsers( + not_null item, + const QVector &users); + +inline constexpr auto kMaxUnreadReactions = 5; // Now 3, but just in case. +using OnStackUsers = std::array; +[[nodiscard]] OnStackUsers LookupRecentUnreadReactedUsers( + not_null item); +void CheckReactionNotificationSchedule( + not_null item, + const OnStackUsers &wasUsers); +[[nodiscard]] MessageFlags NewForwardedFlags( + not_null peer, + PeerId from, + not_null fwd); +[[nodiscard]] MessageFlags FinalizeMessageFlags(MessageFlags flags); +[[nodiscard]] bool CopyMarkupToForward(not_null item); +[[nodiscard]] TextWithEntities EnsureNonEmpty( + const TextWithEntities &text = TextWithEntities()); +[[nodiscard]] TextWithEntities UnsupportedMessageText(); + +void RequestDependentMessageData( + not_null item, + PeerId peerId, + MsgId msgId); +[[nodiscard]] MessageFlags NewMessageFlags(not_null peer); +[[nodiscard]] bool ShouldSendSilent( + not_null peer, + const Api::SendOptions &options); +[[nodiscard]] HistoryItem *LookupReplyTo( + not_null history, + MsgId replyToId); +[[nodiscard]] MsgId LookupReplyToTop(HistoryItem *replyTo); +[[nodiscard]] bool LookupReplyIsTopicPost(HistoryItem *replyTo); + +struct SendingErrorRequest { + MsgId topicRootId = 0; + const HistoryItemsList *forward = nullptr; + const TextWithTags *text = nullptr; + bool ignoreSlowmodeCountdown = false; +}; +[[nodiscard]] QString GetErrorTextForSending( + not_null peer, + SendingErrorRequest request); +[[nodiscard]] QString GetErrorTextForSending( + not_null thread, + SendingErrorRequest request); + +[[nodiscard]] TextWithEntities DropCustomEmoji(TextWithEntities text); + +[[nodiscard]] Main::Session *SessionByUniqueId(uint64 sessionUniqueId); +[[nodiscard]] HistoryItem *MessageByGlobalId(GlobalMsgId globalId); + +[[nodiscard]] QDateTime ItemDateTime(not_null item); +[[nodiscard]] QString ItemDateText( + not_null item, + bool isUntilOnline); +[[nodiscard]] bool IsItemScheduledUntilOnline( + not_null item); + +[[nodiscard]] ClickHandlerPtr JumpToMessageClickHandler( + not_null peer, + MsgId msgId, + FullMsgId returnToId = FullMsgId()); +[[nodiscard]] ClickHandlerPtr JumpToMessageClickHandler( + not_null item, + FullMsgId returnToId = FullMsgId()); + +[[nodiscard]] not_null GenerateJoinedMessage( + not_null history, + TimeId inviteDate, + not_null inviter, + bool viaRequest); + +[[nodiscard]] std::optional PeerHasThisCall( + not_null peer, + CallId id); +[[nodiscard]] rpl::producer PeerHasThisCallValue( + not_null peer, + CallId id); +[[nodiscard]] ClickHandlerPtr GroupCallClickHandler( + not_null peer, + CallId callId); diff --git a/Telegram/SourceFiles/history/history_message.cpp b/Telegram/SourceFiles/history/history_message.cpp deleted file mode 100644 index b9d939634..000000000 --- a/Telegram/SourceFiles/history/history_message.cpp +++ /dev/null @@ -1,1883 +0,0 @@ -/* -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 "history/history_message.h" - -#include "lang/lang_keys.h" -#include "apiwrap.h" -#include "api/api_text_entities.h" -#include "history/history.h" -#include "history/history_service.h" -#include "history/history_item_components.h" -#include "history/history_unread_things.h" -#include "history/view/history_view_service_message.h" -#include "history/view/history_view_context_menu.h" // CopyPostLink. -#include "history/view/media/history_view_media.h" // AddTimestampLinks. -#include "chat_helpers/stickers_emoji_pack.h" -#include "main/main_session.h" -#include "main/main_session_settings.h" -#include "api/api_updates.h" -#include "boxes/share_box.h" -#include "ui/text/text_isolated_emoji.h" -#include "ui/text/format_values.h" -#include "storage/storage_shared_media.h" -#include "mtproto/mtproto_config.h" -#include "data/notify/data_notify_settings.h" -#include "data/data_session.h" -#include "data/data_changes.h" -#include "data/data_media_types.h" -#include "data/data_channel.h" -#include "data/data_forum_topic.h" -#include "data/data_forum.h" -#include "data/data_user.h" -#include "data/data_web_page.h" -#include "data/data_sponsored_messages.h" -#include "data/data_scheduled_messages.h" -#include "styles/style_dialogs.h" -#include "styles/style_widgets.h" -#include "styles/style_chat.h" -#include "styles/style_window.h" - -namespace { - -[[nodiscard]] MessageFlags NewForwardedFlags( - not_null peer, - PeerId from, - not_null fwd) { - auto result = NewMessageFlags(peer); - if (from) { - result |= MessageFlag::HasFromId; - } - if (const auto media = fwd->media()) { - if ((!peer->isChannel() || peer->isMegagroup()) - && media->forwardedBecomesUnread()) { - result |= MessageFlag::MediaIsUnread; - } - } - if (fwd->hasViews()) { - result |= MessageFlag::HasViews; - } - return result; -} - -[[nodiscard]] bool CopyMarkupToForward(not_null item) { - auto mediaOriginal = item->media(); - if (mediaOriginal && mediaOriginal->game()) { - // Copy inline keyboard when forwarding messages with a game. - return true; - } - const auto markup = item->inlineReplyMarkup(); - if (!markup) { - return false; - } - using Type = HistoryMessageMarkupButton::Type; - for (const auto &row : markup->data.rows) { - for (const auto &button : row) { - const auto switchInline = (button.type == Type::SwitchInline) - || (button.type == Type::SwitchInlineSame); - const auto url = (button.type == Type::Url) - || (button.type == Type::Auth); - if ((!switchInline || !item->viaBot()) && !url) { - return false; - } - } - } - return true; -} - -[[nodiscard]] bool HasInlineItems(const HistoryItemsList &items) { - for (const auto &item : items) { - if (item->viaBot()) { - return true; - } - } - return false; -} - -[[nodiscard]] TextWithEntities EnsureNonEmpty( - const TextWithEntities &text = TextWithEntities()) { - return !text.text.isEmpty() ? text : TextWithEntities{ u":-("_q }; -} - -} // namespace - -QString GetErrorTextForSending( - not_null peer, - SendingErrorRequest request) { - const auto forum = request.topicRootId ? peer->forum() : nullptr; - const auto topic = forum - ? forum->topicFor(request.topicRootId) - : nullptr; - if (!(topic ? topic->canWrite() : peer->canWrite())) { - return tr::lng_forward_cant(tr::now); - } - - if (request.forward) { - for (const auto &item : *request.forward) { - if (const auto media = item->media()) { - const auto error = media->errorTextForForward(peer); - if (!error.isEmpty() && error != u"skip"_q) { - return error; - } - } - } - } - const auto error = Data::RestrictionError( - peer, - ChatRestriction::SendInline); - if (error && request.forward && HasInlineItems(*request.forward)) { - return *error; - } - - if (peer->slowmodeApplied()) { - const auto hasText = (request.text && !request.text->empty()); - const auto count = (hasText ? 1 : 0) - + (request.forward ? int(request.forward->size()) : 0); - if (const auto history = peer->owner().historyLoaded(peer)) { - if (!request.ignoreSlowmodeCountdown - && (history->latestSendingMessage() != nullptr) - && (count > 0)) { - return tr::lng_slowmode_no_many(tr::now); - } - } - if (request.text && request.text->text.size() > MaxMessageSize) { - return tr::lng_slowmode_too_long(tr::now); - } else if (hasText && count > 1) { - return tr::lng_slowmode_no_many(tr::now); - } else if (count > 1) { - const auto albumForward = [&] { - const auto first = request.forward->front(); - if (const auto groupId = first->groupId()) { - for (const auto &item : *request.forward) { - if (item->groupId() != groupId) { - return false; - } - } - return true; - } - return false; - }(); - if (!albumForward) { - return tr::lng_slowmode_no_many(tr::now); - } - } - } - if (const auto left = peer->slowmodeSecondsLeft()) { - if (!request.ignoreSlowmodeCountdown) { - return tr::lng_slowmode_enabled( - tr::now, - lt_left, - Ui::FormatDurationWordsSlowmode(left)); - } - } - - return QString(); -} - -QString GetErrorTextForSending( - not_null thread, - SendingErrorRequest request) { - request.topicRootId = thread->topicRootId(); - return GetErrorTextForSending(thread->peer(), std::move(request)); -} - -void RequestDependentMessageData( - not_null item, - PeerId peerId, - MsgId msgId) { - if (!IsServerMsgId(msgId)) { - return; - } - const auto fullId = item->fullId(); - const auto history = item->history(); - const auto session = &history->session(); - const auto done = [=] { - if (const auto item = session->data().message(fullId)) { - item->updateDependencyItem(); - } - }; - history->session().api().requestMessageData( - (peerId ? history->owner().peer(peerId) : history->peer), - msgId, - done); -} - -MessageFlags NewMessageFlags(not_null peer) { - return MessageFlag::BeingSent - | (peer->isSelf() ? MessageFlag() : MessageFlag::Outgoing); -} - -bool ShouldSendSilent( - not_null peer, - const Api::SendOptions &options) { - return options.silent - || (peer->isBroadcast() - && peer->owner().notifySettings().silentPosts(peer)) - || (peer->session().supportMode() - && peer->session().settings().supportAllSilent()); -} - -HistoryItem *LookupReplyTo(not_null history, MsgId replyToId) { - const auto &owner = history->owner(); - return owner.message(history->peer, replyToId); -} - -MsgId LookupReplyToTop(HistoryItem *replyTo) { - return replyTo ? replyTo->replyToTop() : 0; -} - -bool LookupReplyIsTopicPost(HistoryItem *replyTo) { - return replyTo - && (replyTo->topicRootId() != Data::ForumTopic::kGeneralId); -} - -MTPMessageReplyHeader NewMessageReplyHeader(const Api::SendAction &action) { - if (const auto id = action.replyTo) { - const auto to = LookupReplyTo(action.history, id); - if (const auto replyToTop = LookupReplyToTop(to)) { - using Flag = MTPDmessageReplyHeader::Flag; - return MTP_messageReplyHeader( - MTP_flags(Flag::f_reply_to_top_id - | (LookupReplyIsTopicPost(to) - ? Flag::f_forum_topic - : Flag(0))), - MTP_int(id), - MTPPeer(), - MTP_int(replyToTop)); - } - return MTP_messageReplyHeader( - MTP_flags(0), - MTP_int(id), - MTPPeer(), - MTPint()); - } - return MTPMessageReplyHeader(); -} - -TextWithEntities DropCustomEmoji(TextWithEntities text) { - text.entities.erase( - ranges::remove( - text.entities, - EntityType::CustomEmoji, - &EntityInText::type), - text.entities.end()); - return text; -} - -struct HistoryMessage::CreateConfig { - PeerId replyToPeer = 0; - MsgId replyTo = 0; - MsgId replyToTop = 0; - bool replyIsTopicPost = false; - UserId viaBotId = 0; - int viewsCount = -1; - QString author; - PeerId senderOriginal = 0; - QString senderNameOriginal; - QString forwardPsaType; - MsgId originalId = 0; - PeerId savedFromPeer = 0; - MsgId savedFromMsgId = 0; - QString authorOriginal; - TimeId originalDate = 0; - TimeId editDate = 0; - HistoryMessageMarkupData markup; - HistoryMessageRepliesData replies; - bool imported = false; - - // For messages created from existing messages (forwarded). - const HistoryMessageReplyMarkup *inlineMarkup = nullptr; -}; - -void HistoryMessage::FillForwardedInfo( - CreateConfig &config, - const MTPDmessageFwdHeader &data) { - if (const auto fromId = data.vfrom_id()) { - config.senderOriginal = peerFromMTP(*fromId); - } - config.originalDate = data.vdate().v; - config.senderNameOriginal = qs(data.vfrom_name().value_or_empty()); - config.forwardPsaType = qs(data.vpsa_type().value_or_empty()); - config.originalId = data.vchannel_post().value_or_empty(); - config.authorOriginal = qs(data.vpost_author().value_or_empty()); - const auto savedFromPeer = data.vsaved_from_peer(); - const auto savedFromMsgId = data.vsaved_from_msg_id(); - if (savedFromPeer && savedFromMsgId) { - config.savedFromPeer = peerFromMTP(*savedFromPeer); - config.savedFromMsgId = savedFromMsgId->v; - } - config.imported = data.is_imported(); -} - -HistoryMessage::HistoryMessage( - not_null history, - MsgId id, - const MTPDmessage &data, - MessageFlags localFlags) -: HistoryItem( - history, - id, - FlagsFromMTP(id, data.vflags().v, localFlags), - data.vdate().v, - data.vfrom_id() ? peerFromMTP(*data.vfrom_id()) : PeerId(0)) { - auto config = CreateConfig(); - if (const auto forwarded = data.vfwd_from()) { - forwarded->match([&](const MTPDmessageFwdHeader &data) { - FillForwardedInfo(config, data); - }); - } - if (const auto reply = data.vreply_to()) { - reply->match([&](const MTPDmessageReplyHeader &data) { - if (const auto peer = data.vreply_to_peer_id()) { - config.replyToPeer = peerFromMTP(*peer); - if (config.replyToPeer == history->peer->id) { - config.replyToPeer = 0; - } - } - const auto id = data.vreply_to_msg_id().v; - config.replyTo = data.is_reply_to_scheduled() - ? history->owner().scheduledMessages().localMessageId(id) - : id; - config.replyToTop = data.vreply_to_top_id().value_or(id); - config.replyIsTopicPost = data.is_forum_topic(); - }); - } - config.viaBotId = data.vvia_bot_id().value_or_empty(); - config.viewsCount = data.vviews().value_or(-1); - config.replies = isScheduled() - ? HistoryMessageRepliesData() - : HistoryMessageRepliesData(data.vreplies()); - config.markup = HistoryMessageMarkupData(data.vreply_markup()); - config.editDate = data.vedit_date().value_or_empty(); - config.author = qs(data.vpost_author().value_or_empty()); - createComponents(std::move(config)); - - if (const auto media = data.vmedia()) { - setMedia(*media); - } - auto textWithEntities = TextWithEntities{ - qs(data.vmessage()), - Api::EntitiesFromMTP( - &history->session(), - data.ventities().value_or_empty()) - }; - setText(_media ? textWithEntities : EnsureNonEmpty(textWithEntities)); - if (const auto groupedId = data.vgrouped_id()) { - setGroupId( - MessageGroupId::FromRaw(history->peer->id, groupedId->v)); - } - setReactions(data.vreactions()); - applyTTL(data); -} - -HistoryMessage::HistoryMessage( - not_null history, - MsgId id, - const MTPDmessageService &data, - MessageFlags localFlags) -: HistoryItem( - history, - id, - FlagsFromMTP(id, data.vflags().v, localFlags), - data.vdate().v, - data.vfrom_id() ? peerFromMTP(*data.vfrom_id()) : PeerId(0)) { - auto config = CreateConfig(); - if (const auto reply = data.vreply_to()) { - reply->match([&](const MTPDmessageReplyHeader &data) { - const auto peer = data.vreply_to_peer_id() - ? peerFromMTP(*data.vreply_to_peer_id()) - : history->peer->id; - if (!peer || peer == history->peer->id) { - const auto id = data.vreply_to_msg_id().v; - config.replyTo = id; - config.replyToTop = data.vreply_to_top_id().value_or(id); - } - }); - } - createComponents(std::move(config)); - - data.vaction().match([&](const MTPDmessageActionPhoneCall &data) { - _media = std::make_unique( - this, - Data::ComputeCallData(data)); - setTextValue({}); - }, [](const auto &) { - Unexpected("Service message action type in HistoryMessage."); - }); - - applyTTL(data); -} - -HistoryMessage::HistoryMessage( - not_null history, - MsgId id, - MessageFlags flags, - TimeId date, - PeerId from, - const QString &postAuthor, - not_null original, - MsgId topicRootId) -: HistoryItem( - history, - id, - (NewForwardedFlags(history->peer, from, original) | flags), - date, - from) { - const auto peer = history->peer; - - auto config = CreateConfig(); - - const auto originalMedia = original->media(); - const auto dropForwardInfo = original->computeDropForwardedInfo(); - config.replyTo = config.replyToTop = topicRootId; - config.replyIsTopicPost = (topicRootId != 0); - if (!dropForwardInfo) { - config.originalDate = original->dateOriginal(); - if (const auto info = original->hiddenSenderInfo()) { - config.senderNameOriginal = info->name; - } else if (const auto senderOriginal = original->senderOriginal()) { - config.senderOriginal = senderOriginal->id; - if (senderOriginal->isChannel()) { - config.originalId = original->idOriginal(); - } - } else { - Unexpected("Corrupt forwarded information in message."); - } - config.authorOriginal = original->authorOriginal(); - } - if (peer->isSelf()) { - // - // iOS app sends you to the original post if we forward a forward from channel. - // But server returns not the original post but the forward in saved_from_... - // - //if (config.originalId) { - // config.savedFromPeer = config.senderOriginal; - // config.savedFromMsgId = config.originalId; - //} else { - config.savedFromPeer = original->history()->peer->id; - config.savedFromMsgId = original->id; - //} - } - if (flags & MessageFlag::HasPostAuthor) { - config.author = postAuthor; - } - if (const auto fwdViaBot = original->viaBot()) { - config.viaBotId = peerToUser(fwdViaBot->id); - } else if (originalMedia && originalMedia->game()) { - if (const auto sender = original->senderOriginal()) { - if (const auto user = sender->asUser()) { - if (user->isBot()) { - config.viaBotId = peerToUser(user->id); - } - } - } - } - const auto fwdViewsCount = original->viewsCount(); - if (fwdViewsCount > 0) { - config.viewsCount = fwdViewsCount; - } else if ((isPost() && !isScheduled()) - || (original->senderOriginal() - && original->senderOriginal()->isChannel())) { - config.viewsCount = 1; - } - - const auto mediaOriginal = original->media(); - if (CopyMarkupToForward(original)) { - config.inlineMarkup = original->inlineReplyMarkup(); - } - createComponents(std::move(config)); - - const auto ignoreMedia = [&] { - if (mediaOriginal && mediaOriginal->webpage()) { - if (peer->amRestricted(ChatRestriction::EmbedLinks)) { - return true; - } - } - return false; - }; - if (mediaOriginal && !ignoreMedia()) { - _media = mediaOriginal->clone(this); - } - - const auto dropCustomEmoji = dropForwardInfo - && !history->session().premium() - && !history->peer->isSelf(); - setText(dropCustomEmoji - ? DropCustomEmoji(original->originalText()) - : original->originalText()); -} - -HistoryMessage::HistoryMessage( - not_null history, - MsgId id, - MessageFlags flags, - MsgId replyTo, - UserId viaBotId, - TimeId date, - PeerId from, - const QString &postAuthor, - const TextWithEntities &textWithEntities, - const MTPMessageMedia &media, - HistoryMessageMarkupData &&markup, - uint64 groupedId) -: HistoryItem( - history, - id, - flags, - date, - (flags & MessageFlag::HasFromId) ? from : 0) { - createComponentsHelper( - flags, - replyTo, - viaBotId, - postAuthor, - std::move(markup)); - setMedia(media); - setText(textWithEntities); - if (groupedId) { - setGroupId(MessageGroupId::FromRaw(history->peer->id, groupedId)); - } -} - -HistoryMessage::HistoryMessage( - not_null history, - MsgId id, - MessageFlags flags, - MsgId replyTo, - UserId viaBotId, - TimeId date, - PeerId from, - const QString &postAuthor, - not_null document, - const TextWithEntities &caption, - HistoryMessageMarkupData &&markup) -: HistoryItem( - history, - id, - flags, - date, - (flags & MessageFlag::HasFromId) ? from : 0) { - createComponentsHelper( - flags, - replyTo, - viaBotId, - postAuthor, - std::move(markup)); - - const auto skipPremiumEffect = !history->session().premium(); - _media = std::make_unique( - this, - document, - skipPremiumEffect); - setText(caption); -} - -HistoryMessage::HistoryMessage( - not_null history, - MsgId id, - MessageFlags flags, - MsgId replyTo, - UserId viaBotId, - TimeId date, - PeerId from, - const QString &postAuthor, - not_null photo, - const TextWithEntities &caption, - HistoryMessageMarkupData &&markup) -: HistoryItem( - history, - id, - flags, - date, - (flags & MessageFlag::HasFromId) ? from : 0) { - createComponentsHelper( - flags, - replyTo, - viaBotId, - postAuthor, - std::move(markup)); - - _media = std::make_unique(this, photo); - setText(caption); -} - -HistoryMessage::HistoryMessage( - not_null history, - MsgId id, - MessageFlags flags, - MsgId replyTo, - UserId viaBotId, - TimeId date, - PeerId from, - const QString &postAuthor, - not_null game, - HistoryMessageMarkupData &&markup) -: HistoryItem( - history, - id, - flags, - date, - (flags & MessageFlag::HasFromId) ? from : 0) { - createComponentsHelper( - flags, - replyTo, - viaBotId, - postAuthor, - std::move(markup)); - - _media = std::make_unique(this, game); - setTextValue({}); -} - -HistoryMessage::HistoryMessage( - not_null history, - MsgId id, - Data::SponsoredFrom from, - const TextWithEntities &textWithEntities, - HistoryItem *injectedAfter) -: HistoryItem( - history, - id, - ((history->peer->isChannel() ? MessageFlag::Post : MessageFlag(0)) - //| (from.peer ? MessageFlag::HasFromId : MessageFlag(0)) - | MessageFlag::Local), - HistoryItem::NewMessageDate(injectedAfter - ? injectedAfter->date() - : 0), - /*from.peer ? from.peer->id : */PeerId(0)) { - createComponentsHelper( - _flags, - MsgId(0), // replyTo - UserId(0), // viaBotId - QString(), // postAuthor - HistoryMessageMarkupData()); - setText(textWithEntities); - setSponsoredFrom(from); -} - -void HistoryMessage::createComponentsHelper( - MessageFlags flags, - MsgId replyTo, - UserId viaBotId, - const QString &postAuthor, - HistoryMessageMarkupData &&markup) { - auto config = CreateConfig(); - config.viaBotId = viaBotId; - if (flags & MessageFlag::HasReplyInfo) { - config.replyTo = replyTo; - const auto to = LookupReplyTo(history(), replyTo); - const auto replyToTop = LookupReplyToTop(to); - config.replyToTop = replyToTop ? replyToTop : replyTo; - const auto forum = history()->asForum(); - config.replyIsTopicPost = LookupReplyIsTopicPost(to) - || (to && to->Has()) - || (forum && forum->creating(config.replyToTop)); - } - config.markup = std::move(markup); - if (flags & MessageFlag::HasPostAuthor) config.author = postAuthor; - if (flags & MessageFlag::HasViews) config.viewsCount = 1; - - createComponents(std::move(config)); -} - -int HistoryMessage::viewsCount() const { - if (const auto views = Get()) { - return std::max(views->views.count, 0); - } - return HistoryItem::viewsCount(); -} - -bool HistoryMessage::checkCommentsLinkedChat(ChannelId id) const { - if (!id) { - return true; - } else if (const auto channel = history()->peer->asChannel()) { - if (channel->linkedChatKnown() - || !(channel->flags() & ChannelDataFlag::HasLink)) { - const auto linked = channel->linkedChat(); - if (!linked || peerToChannel(linked->id) != id) { - return false; - } - } - return true; - } - return false; -} - -int HistoryMessage::repliesCount() const { - if (const auto views = Get()) { - if (!checkCommentsLinkedChat(views->commentsMegagroupId)) { - return 0; - } - return std::max(views->replies.count, 0); - } - return HistoryItem::repliesCount(); -} - -bool HistoryMessage::repliesAreComments() const { - if (const auto views = Get()) { - return (views->commentsMegagroupId != 0) - && checkCommentsLinkedChat(views->commentsMegagroupId); - } - return HistoryItem::repliesAreComments(); -} - -bool HistoryMessage::externalReply() const { - if (!history()->peer->isRepliesChat()) { - return false; - } else if (const auto forwarded = Get()) { - return forwarded->savedFromPeer && forwarded->savedFromMsgId; - } - return false; -} - -void HistoryMessage::setCommentsInboxReadTill(MsgId readTillId) { - const auto views = Get(); - if (!views) { - return; - } - const auto newReadTillId = std::max(readTillId.bare, int64(1)); - const auto ignore = (newReadTillId < views->commentsInboxReadTillId); - if (ignore) { - return; - } - const auto changed = (newReadTillId > views->commentsInboxReadTillId); - if (!changed) { - return; - } - const auto wasUnread = areCommentsUnread(); - views->commentsInboxReadTillId = newReadTillId; - if (wasUnread && !areCommentsUnread()) { - history()->owner().requestItemRepaint(this); - } -} - -void HistoryMessage::setCommentsMaxId(MsgId maxId) { - if (const auto views = Get()) { - if (views->commentsMaxId != maxId) { - const auto wasUnread = areCommentsUnread(); - views->commentsMaxId = maxId; - if (wasUnread != areCommentsUnread()) { - history()->owner().requestItemRepaint(this); - } - } - } -} - -void HistoryMessage::setCommentsPossibleMaxId(MsgId possibleMaxId) { - if (const auto views = Get()) { - if (views->commentsMaxId < possibleMaxId) { - const auto wasUnread = areCommentsUnread(); - views->commentsMaxId = possibleMaxId; - if (!wasUnread && areCommentsUnread()) { - history()->owner().requestItemRepaint(this); - } - } - } -} - -bool HistoryMessage::areCommentsUnread() const { - const auto views = Get(); - if (!views - || !views->commentsMegagroupId - || !checkCommentsLinkedChat(views->commentsMegagroupId)) { - return false; - } - const auto till = views->commentsInboxReadTillId; - if (views->commentsInboxReadTillId < 2 || views->commentsMaxId <= till) { - return false; - } - const auto group = views->commentsMegagroupId - ? history()->owner().historyLoaded( - peerFromChannel(views->commentsMegagroupId)) - : history().get(); - return !group || (views->commentsMaxId > group->inboxReadTillId()); -} - -FullMsgId HistoryMessage::commentsItemId() const { - if (const auto views = Get()) { - return FullMsgId( - PeerId(views->commentsMegagroupId), - views->commentsRootId); - } - return FullMsgId(); -} - -void HistoryMessage::setCommentsItemId(FullMsgId id) { - if (id.peer == _history->peer->id) { - if (id.msg != this->id) { - if (const auto reply = Get()) { - reply->replyToMsgTop = id.msg; - } - } - } else if (const auto views = Get()) { - if (const auto channelId = peerToChannel(id.peer)) { - if (views->commentsMegagroupId != channelId) { - views->commentsMegagroupId = channelId; - history()->owner().requestItemResize(this); - } - views->commentsRootId = id.msg; - } - } -} - -bool HistoryMessage::updateDependencyItem() { - if (const auto reply = Get()) { - const auto documentId = reply->replyToDocumentId; - const auto webpageId = reply->replyToWebPageId; - const auto result = reply->updateData(this, true); - const auto mediaIdChanged = (documentId != reply->replyToDocumentId) - || (webpageId != reply->replyToWebPageId); - if (mediaIdChanged && generateLocalEntitiesByReply()) { - history()->owner().requestItemTextRefresh(this); - } - return result; - } - return true; -} - -void HistoryMessage::applySentMessage(const MTPDmessage &data) { - HistoryItem::applySentMessage(data); - - if (const auto period = data.vttl_period(); period && period->v > 0) { - applyTTL(data.vdate().v + period->v); - } else { - applyTTL(0); - } -} - -void HistoryMessage::applySentMessage( - const QString &text, - const MTPDupdateShortSentMessage &data, - bool wasAlready) { - HistoryItem::applySentMessage(text, data, wasAlready); - - if (const auto period = data.vttl_period(); period && period->v > 0) { - applyTTL(data.vdate().v + period->v); - } else { - applyTTL(0); - } -} - -bool HistoryMessage::allowsForward() const { - return isRegular() - && !forbidsForward() - && history()->peer->allowsForwarding() - && (!_media || _media->allowsForward()); -} - -bool HistoryMessage::allowsSendNow() const { - return isScheduled() && !isSending() && !hasFailed() && !isEditingMedia(); -} - -bool HistoryMessage::isTooOldForEdit(TimeId now) const { - return !_history->peer->canEditMessagesIndefinitely() - && !isScheduled() - && (now - date() >= _history->session().serverConfig().editTimeLimit); -} - -bool HistoryMessage::allowsEdit(TimeId now) const { - return canBeEdited() - && !isTooOldForEdit(now) - && (!_media || _media->allowsEdit()) - && !isLegacyMessage() - && !isEditingMedia(); -} - -void HistoryMessage::createComponents(CreateConfig &&config) { - uint64 mask = 0; - if (config.replyTo) { - mask |= HistoryMessageReply::Bit(); - } - if (config.viaBotId) { - mask |= HistoryMessageVia::Bit(); - } - if (config.viewsCount >= 0 || !config.replies.isNull) { - mask |= HistoryMessageViews::Bit(); - } - if (!config.author.isEmpty()) { - mask |= HistoryMessageSigned::Bit(); - } else if (_history->peer->isMegagroup() // Discussion posts signatures. - && config.savedFromPeer - && !config.authorOriginal.isEmpty()) { - const auto savedFrom = _history->owner().peerLoaded( - config.savedFromPeer); - if (savedFrom && savedFrom->isChannel()) { - mask |= HistoryMessageSigned::Bit(); - } - } else if ((_history->peer->isSelf() || _history->peer->isRepliesChat()) - && !config.authorOriginal.isEmpty()) { - mask |= HistoryMessageSigned::Bit(); - } - if (config.editDate != TimeId(0)) { - mask |= HistoryMessageEdited::Bit(); - } - if (config.originalDate != 0) { - mask |= HistoryMessageForwarded::Bit(); - } - if (!config.markup.isTrivial()) { - mask |= HistoryMessageReplyMarkup::Bit(); - } else if (config.inlineMarkup) { - mask |= HistoryMessageReplyMarkup::Bit(); - } - - UpdateComponents(mask); - - if (const auto reply = Get()) { - reply->replyToPeerId = config.replyToPeer; - reply->replyToMsgId = config.replyTo; - reply->replyToMsgTop = isScheduled() ? 0 : config.replyToTop; - reply->topicPost = config.replyIsTopicPost; - if (!reply->updateData(this)) { - RequestDependentMessageData( - this, - reply->replyToPeerId, - reply->replyToMsgId); - } - } - if (const auto via = Get()) { - via->create(&history()->owner(), config.viaBotId); - } - if (const auto views = Get()) { - changeViewsCount(config.viewsCount); - if (config.replies.isNull - && isSending() - && config.markup.isNull()) { - if (const auto broadcast = history()->peer->asBroadcast()) { - if (const auto linked = broadcast->linkedChat()) { - config.replies.isNull = false; - config.replies.channelId = peerToChannel(linked->id); - } - } - } - setReplies(std::move(config.replies)); - } - if (const auto edited = Get()) { - edited->date = config.editDate; - } - if (const auto msgsigned = Get()) { - msgsigned->author = config.author.isEmpty() - ? config.authorOriginal - : config.author; - msgsigned->isAnonymousRank = !isDiscussionPost() - && author()->isMegagroup(); - } - setupForwardedComponent(config); - if (const auto markup = Get()) { - if (!config.markup.isTrivial()) { - markup->updateData(std::move(config.markup)); - } else if (config.inlineMarkup) { - markup->createForwarded(*config.inlineMarkup); - } - if (markup->data.flags & ReplyMarkupFlag::HasSwitchInlineButton) { - _flags |= MessageFlag::HasSwitchInlineButton; - } - } else if (!config.markup.isNull()) { - _flags |= MessageFlag::HasReplyMarkup; - } else { - _flags &= ~MessageFlag::HasReplyMarkup; - } -} - -bool HistoryMessage::checkRepliesPts( - const HistoryMessageRepliesData &data) const { - const auto channel = history()->peer->asChannel(); - const auto pts = channel - ? channel->pts() - : history()->session().updates().pts(); - return (data.pts >= pts); -} - -void HistoryMessage::setupForwardedComponent(const CreateConfig &config) { - const auto forwarded = Get(); - if (!forwarded) { - return; - } - forwarded->originalDate = config.originalDate; - const auto originalSender = config.senderOriginal - ? config.senderOriginal - : !config.senderNameOriginal.isEmpty() - ? PeerId() - : from()->id; - forwarded->originalSender = originalSender - ? history()->owner().peer(originalSender).get() - : nullptr; - if (!forwarded->originalSender) { - forwarded->hiddenSenderInfo = std::make_unique( - config.senderNameOriginal, - config.imported); - } - forwarded->originalId = config.originalId; - forwarded->originalAuthor = config.authorOriginal; - forwarded->psaType = config.forwardPsaType; - forwarded->savedFromPeer = history()->owner().peerLoaded( - config.savedFromPeer); - forwarded->savedFromMsgId = config.savedFromMsgId; - forwarded->imported = config.imported; -} - -void HistoryMessage::refreshMedia(const MTPMessageMedia *media) { - const auto was = (_media != nullptr); - if (const auto invoice = was ? _media->invoice() : nullptr) { - if (invoice->extendedMedia) { - return; - } - } - _media = nullptr; - if (media) { - setMedia(*media); - } - if (was || _media) { - if (const auto views = Get()) { - refreshRepliesText(views); - } - } -} - -void HistoryMessage::refreshSentMedia(const MTPMessageMedia *media) { - const auto wasGrouped = history()->owner().groups().isGrouped(this); - refreshMedia(media); - if (wasGrouped) { - history()->owner().groups().refreshMessage(this); - } else { - history()->owner().requestItemViewRefresh(this); - } -} - -void HistoryMessage::returnSavedMedia() { - if (!isEditingMedia()) { - return; - } - const auto wasGrouped = history()->owner().groups().isGrouped(this); - _media = std::move(_savedLocalEditMediaData->media); - setText(_savedLocalEditMediaData->text); - clearSavedMedia(); - if (wasGrouped) { - history()->owner().groups().refreshMessage(this, true); - } else { - history()->owner().requestItemViewRefresh(this); - history()->owner().updateDependentMessages(this); - } -} - -void HistoryMessage::setMedia(const MTPMessageMedia &media) { - _media = CreateMedia(this, media); - checkBuyButton(); -} - -void HistoryMessage::checkBuyButton() { - if (const auto invoice = _media ? _media->invoice() : nullptr) { - if (invoice->receiptMsgId) { - replaceBuyWithReceiptInMarkup(); - } - } -} - -std::unique_ptr HistoryMessage::CreateMedia( - not_null item, - const MTPMessageMedia &media) { - using Result = std::unique_ptr; - return media.match([&](const MTPDmessageMediaContact &media) -> Result { - return std::make_unique( - item, - media.vuser_id().v, - qs(media.vfirst_name()), - qs(media.vlast_name()), - qs(media.vphone_number())); - }, [&](const MTPDmessageMediaGeo &media) -> Result { - return media.vgeo().match([&](const MTPDgeoPoint &point) -> Result { - return std::make_unique( - item, - Data::LocationPoint(point)); - }, [](const MTPDgeoPointEmpty &) -> Result { - return nullptr; - }); - }, [&](const MTPDmessageMediaGeoLive &media) -> Result { - return media.vgeo().match([&](const MTPDgeoPoint &point) -> Result { - return std::make_unique( - item, - Data::LocationPoint(point)); - }, [](const MTPDgeoPointEmpty &) -> Result { - return nullptr; - }); - }, [&](const MTPDmessageMediaVenue &media) -> Result { - return media.vgeo().match([&](const MTPDgeoPoint &point) -> Result { - return std::make_unique( - item, - Data::LocationPoint(point), - qs(media.vtitle()), - qs(media.vaddress())); - }, [](const MTPDgeoPointEmpty &data) -> Result { - return nullptr; - }); - }, [&](const MTPDmessageMediaPhoto &media) -> Result { - const auto photo = media.vphoto(); - if (media.vttl_seconds()) { - LOG(("App Error: " - "Unexpected MTPMessageMediaPhoto " - "with ttl_seconds in HistoryMessage.")); - return nullptr; - } else if (!photo) { - LOG(("API Error: " - "Got MTPMessageMediaPhoto " - "without photo and without ttl_seconds.")); - return nullptr; - } - return photo->match([&](const MTPDphoto &photo) -> Result { - return std::make_unique( - item, - item->history()->owner().processPhoto(photo)); - }, [](const MTPDphotoEmpty &) -> Result { - return nullptr; - }); - }, [&](const MTPDmessageMediaDocument &media) -> Result { - const auto document = media.vdocument(); - if (media.vttl_seconds()) { - LOG(("App Error: " - "Unexpected MTPMessageMediaDocument " - "with ttl_seconds in HistoryMessage.")); - return nullptr; - } else if (!document) { - LOG(("API Error: " - "Got MTPMessageMediaDocument " - "without document and without ttl_seconds.")); - return nullptr; - } - return document->match([&](const MTPDdocument &document) -> Result { - return std::make_unique( - item, - item->history()->owner().processDocument(document), - media.is_nopremium()); - }, [](const MTPDdocumentEmpty &) -> Result { - return nullptr; - }); - }, [&](const MTPDmessageMediaWebPage &media) { - return media.vwebpage().match([](const MTPDwebPageEmpty &) -> Result { - return nullptr; - }, [&](const MTPDwebPagePending &webpage) -> Result { - return std::make_unique( - item, - item->history()->owner().processWebpage(webpage)); - }, [&](const MTPDwebPage &webpage) -> Result { - return std::make_unique( - item, - item->history()->owner().processWebpage(webpage)); - }, [](const MTPDwebPageNotModified &) -> Result { - LOG(("API Error: " - "webPageNotModified is unexpected in message media.")); - return nullptr; - }); - }, [&](const MTPDmessageMediaGame &media) -> Result { - return media.vgame().match([&](const MTPDgame &game) { - return std::make_unique( - item, - item->history()->owner().processGame(game)); - }); - }, [&](const MTPDmessageMediaInvoice &media) -> Result { - return std::make_unique( - item, - Data::ComputeInvoiceData(item, media)); - }, [&](const MTPDmessageMediaPoll &media) -> Result { - return std::make_unique( - item, - item->history()->owner().processPoll(media)); - }, [&](const MTPDmessageMediaDice &media) -> Result { - return std::make_unique( - item, - qs(media.vemoticon()), - media.vvalue().v); - }, [](const MTPDmessageMediaEmpty &) -> Result { - return nullptr; - }, [](const MTPDmessageMediaUnsupported &) -> Result { - return nullptr; - }); -} - -void HistoryMessage::replaceBuyWithReceiptInMarkup() { - if (const auto markup = inlineReplyMarkup()) { - for (auto &row : markup->data.rows) { - for (auto &button : row) { - if (button.type == HistoryMessageMarkupButton::Type::Buy) { - const auto receipt = tr::lng_payments_receipt_button(tr::now); - if (button.text != receipt) { - button.text = receipt; - if (markup->inlineKeyboard) { - markup->inlineKeyboard = nullptr; - history()->owner().requestItemResize(this); - } - } - } - } - } - } -} - -void HistoryMessage::applyEdition(HistoryMessageEdition &&edition) { - int keyboardTop = -1; - //if (!pendingResize()) {// #TODO edit bot message - // if (auto keyboard = inlineReplyKeyboard()) { - // int h = st::msgBotKbButton.margin + keyboard->naturalHeight(); - // keyboardTop = _height - h + st::msgBotKbButton.margin - marginBottom(); - // } - //} - - if (edition.isEditHide) { - _flags |= MessageFlag::HideEdited; - } else { - _flags &= ~MessageFlag::HideEdited; - } - - if (edition.editDate != -1) { - //_flags |= MTPDmessage::Flag::f_edit_date; - if (!Has()) { - AddComponents(HistoryMessageEdited::Bit()); - } - auto edited = Get(); - edited->date = edition.editDate; - } - - if (!edition.useSameMarkup) { - setReplyMarkup(base::take(edition.replyMarkup)); - } - if (!isLocalUpdateMedia()) { - refreshMedia(edition.mtpMedia); - } - if (!edition.useSameReactions) { - updateReactions(edition.mtpReactions); - } - changeViewsCount(edition.views); - setForwardsCount(edition.forwards); - setText(_media - ? edition.textWithEntities - : EnsureNonEmpty(edition.textWithEntities)); - if (!edition.useSameReplies) { - if (!edition.replies.isNull) { - if (checkRepliesPts(edition.replies)) { - setReplies(base::take(edition.replies)); - } - } else { - clearReplies(); - } - } - - applyTTL(edition.ttl); - - finishEdition(keyboardTop); -} - -void HistoryMessage::applyEdition(const MTPDmessageService &message) { - if (message.vaction().type() == mtpc_messageActionHistoryClear) { - const auto wasGrouped = history()->owner().groups().isGrouped(this); - setReplyMarkup({}); - refreshMedia(nullptr); - setTextValue({}); - changeViewsCount(-1); - setForwardsCount(-1); - if (wasGrouped) { - history()->owner().groups().unregisterMessage(this); - } - finishEditionToEmpty(); - } -} - -void HistoryMessage::applyEdition(const MTPMessageExtendedMedia &media) { - if (const auto existing = this->media()) { - if (existing->updateExtendedMedia(this, media)) { - checkBuyButton(); - finishEdition(-1); - } - } -} - -void HistoryMessage::updateSentContent( - const TextWithEntities &textWithEntities, - const MTPMessageMedia *media) { - setText(textWithEntities); - if (_flags & MessageFlag::FromInlineBot) { - if (!media || !_media || !_media->updateInlineResultMedia(*media)) { - refreshSentMedia(media); - } - _flags &= ~MessageFlag::FromInlineBot; - } else if (media || _media) { - if (!media || !_media || !_media->updateSentMedia(*media)) { - refreshSentMedia(media); - } - } - history()->owner().requestItemResize(this); -} - -void HistoryMessage::updateForwardedInfo(const MTPMessageFwdHeader *fwd) { - const auto forwarded = Get(); - if (!fwd) { - if (forwarded) { - LOG(("API Error: Server removed forwarded information.")); - } - return; - } else if (!forwarded) { - LOG(("API Error: Server added forwarded information.")); - return; - } - fwd->match([&](const MTPDmessageFwdHeader &data) { - auto config = CreateConfig(); - FillForwardedInfo(config, data); - setupForwardedComponent(config); - history()->owner().requestItemResize(this); - }); -} - -void HistoryMessage::updateReplyMarkup(HistoryMessageMarkupData &&markup) { - setReplyMarkup(std::move(markup)); -} - -void HistoryMessage::contributeToSlowmode(TimeId realDate) { - if (const auto channel = history()->peer->asChannel()) { - if (out() && isRegular()) { - channel->growSlowmodeLastMessage(realDate ? realDate : date()); - } - } -} - -void HistoryMessage::addToUnreadThings(HistoryUnreadThings::AddType type) { - if (!isRegular()) { - return; - } - const auto mention = isUnreadMention(); - const auto reaction = hasUnreadReaction(); - if (!mention && !reaction) { - return; - } - const auto topic = this->topic(); - const auto history = this->history(); - const auto changes = &history->session().changes(); - if (mention) { - if (history->unreadMentions().add(id, type)) { - changes->historyUpdated( - history, - Data::HistoryUpdate::Flag::UnreadMentions); - } - if (topic && topic->unreadMentions().add(id, type)) { - changes->topicUpdated( - topic, - Data::TopicUpdate::Flag::UnreadMentions); - } - } - if (reaction) { - const auto toHistory = history->unreadReactions().add(id, type); - const auto toTopic = topic && topic->unreadReactions().add(id, type); - if (toHistory || toTopic) { - if (type == HistoryUnreadThings::AddType::New) { - changes->messageUpdated( - this, - Data::MessageUpdate::Flag::NewUnreadReaction); - } - if (hasUnreadReaction()) { - if (toHistory) { - changes->historyUpdated( - history, - Data::HistoryUpdate::Flag::UnreadReactions); - } - if (toTopic) { - changes->topicUpdated( - topic, - Data::TopicUpdate::Flag::UnreadReactions); - } - } - } - } -} - -void HistoryMessage::destroyHistoryEntry() { - if (isUnreadMention()) { - history()->unreadMentions().erase(id); - if (const auto topic = this->topic()) { - topic->unreadMentions().erase(id); - } - } - if (hasUnreadReaction()) { - history()->unreadReactions().erase(id); - if (const auto topic = this->topic()) { - topic->unreadReactions().erase(id); - } - } - if (const auto reply = Get()) { - changeReplyToTopCounter(reply, -1); - } -} - -Storage::SharedMediaTypesMask HistoryMessage::sharedMediaTypes() const { - auto result = Storage::SharedMediaTypesMask {}; - if (const auto media = this->media()) { - result.set(media->sharedMediaTypes()); - } - if (hasTextLinks()) { - result.set(Storage::SharedMediaType::Link); - } - if (isPinned()) { - result.set(Storage::SharedMediaType::Pinned); - } - return result; -} - -bool HistoryMessage::generateLocalEntitiesByReply() const { - using namespace HistoryView; - if (!_media) { - return true; - } else if (const auto document = _media->document()) { - return !DurationForTimestampLinks(document); - } else if (const auto webpage = _media->webpage()) { - return (webpage->type != WebPageType::Video) - && !DurationForTimestampLinks(webpage); - } - return true; -} - -TextWithEntities HistoryMessage::withLocalEntities( - const TextWithEntities &textWithEntities) const { - using namespace HistoryView; - if (!generateLocalEntitiesByReply()) { - if (!_media) { - } else if (const auto document = _media->document()) { - if (const auto duration = DurationForTimestampLinks(document)) { - return AddTimestampLinks( - textWithEntities, - duration, - TimestampLinkBase(document, fullId())); - } - } else if (const auto webpage = _media->webpage()) { - if (const auto duration = DurationForTimestampLinks(webpage)) { - return AddTimestampLinks( - textWithEntities, - duration, - TimestampLinkBase(webpage, fullId())); - } - } - return textWithEntities; - } - if (const auto reply = Get()) { - const auto document = reply->replyToDocumentId - ? history()->owner().document(reply->replyToDocumentId).get() - : nullptr; - const auto webpage = reply->replyToWebPageId - ? history()->owner().webpage(reply->replyToWebPageId).get() - : nullptr; - if (document) { - if (const auto duration = DurationForTimestampLinks(document)) { - const auto context = reply->replyToMsg->fullId(); - return AddTimestampLinks( - textWithEntities, - duration, - TimestampLinkBase(document, context)); - } - } else if (webpage) { - if (const auto duration = DurationForTimestampLinks(webpage)) { - const auto context = reply->replyToMsg->fullId(); - return AddTimestampLinks( - textWithEntities, - duration, - TimestampLinkBase(webpage, context)); - } - } - } - return textWithEntities; -} - -MsgId HistoryMessage::replyToId() const { - if (const auto reply = Get()) { - return reply->replyToId(); - } - return 0; -} - -MsgId HistoryMessage::replyToTop() const { - if (const auto reply = Get()) { - return reply->replyToTop(); - } - return 0; -} - -MsgId HistoryMessage::topicRootId() const { - if (const auto reply = Get() - ; reply && reply->topicPost) { - return reply->replyToTop(); - } - return Data::ForumTopic::kGeneralId; -} - -void HistoryMessage::setText(const TextWithEntities &textWithEntities) { - for (const auto &entity : textWithEntities.entities) { - auto type = entity.type(); - if (type == EntityType::Url - || type == EntityType::CustomUrl - || type == EntityType::Email) { - _flags |= MessageFlag::HasTextLinks; - break; - } - } - setTextValue((_media && _media->consumeMessageText(textWithEntities)) - ? TextWithEntities() - : std::move(textWithEntities)); -} - -void HistoryMessage::setTextValue(TextWithEntities text) { - const auto had = !_text.empty(); - _text = std::move(text); - if (had) { - history()->owner().requestItemTextRefresh(this); - } -} - -void HistoryMessage::setReplyMarkup(HistoryMessageMarkupData &&markup) { - const auto requestUpdate = [&] { - history()->owner().requestItemResize(this); - history()->session().changes().messageUpdated( - this, - Data::MessageUpdate::Flag::ReplyMarkup); - }; - if (markup.isNull()) { - if (_flags & MessageFlag::HasReplyMarkup) { - _flags &= ~MessageFlag::HasReplyMarkup; - if (Has()) { - RemoveComponents(HistoryMessageReplyMarkup::Bit()); - } - requestUpdate(); - } - return; - } - - // optimization: don't create markup component for the case - // MTPDreplyKeyboardHide with flags = 0, assume it has f_zero flag - if (markup.isTrivial()) { - bool changed = false; - if (Has()) { - RemoveComponents(HistoryMessageReplyMarkup::Bit()); - changed = true; - } - if (!(_flags & MessageFlag::HasReplyMarkup)) { - _flags |= MessageFlag::HasReplyMarkup; - changed = true; - } - if (changed) { - requestUpdate(); - } - } else { - if (!(_flags & MessageFlag::HasReplyMarkup)) { - _flags |= MessageFlag::HasReplyMarkup; - } - if (!Has()) { - AddComponents(HistoryMessageReplyMarkup::Bit()); - } - Get()->updateData(std::move(markup)); - requestUpdate(); - } -} - -TextWithEntities HistoryMessage::originalText() const { - return _text; -} - -TextWithEntities HistoryMessage::originalTextWithLocalEntities() const { - return withLocalEntities(originalText()); -} - -TextForMimeData HistoryMessage::clipboardText() const { - return TextForMimeData::WithExpandedLinks(_text); -} - -bool HistoryMessage::changeViewsCount(int count) { - const auto views = Get(); - if (!views - || views->views.count == count - || (count >= 0 && views->views.count > count)) { - return false; - } - - views->views.count = count; - return true; -} - -void HistoryMessage::setForwardsCount(int count) { - const auto views = Get(); - if (!views - || views->forwardsCount == count - || (count >= 0 && views->forwardsCount > count)) { - return; - } - - views->forwardsCount = count; - history()->owner().notifyItemDataChange(this); -} - -void HistoryMessage::setPostAuthor(const QString &author) { - auto msgsigned = Get(); - if (author.isEmpty()) { - if (!msgsigned) { - return; - } - RemoveComponents(HistoryMessageSigned::Bit()); - history()->owner().requestItemResize(this); - return; - } - if (!msgsigned) { - AddComponents(HistoryMessageSigned::Bit()); - msgsigned = Get(); - } else if (msgsigned->author == author) { - return; - } - msgsigned->author = author; - msgsigned->isAnonymousRank = !isDiscussionPost() - && this->author()->isMegagroup(); - history()->owner().requestItemResize(this); -} - -void HistoryMessage::setReplies(HistoryMessageRepliesData &&data) { - if (data.isNull) { - return; - } - auto views = Get(); - if (!views) { - AddComponents(HistoryMessageViews::Bit()); - views = Get(); - } - const auto &repliers = data.recentRepliers; - const auto count = data.repliesCount; - const auto channelId = data.channelId; - const auto readTillId = data.readMaxId - ? std::max({ - views->commentsInboxReadTillId.bare, - data.readMaxId.bare, - int64(1), - }) - : views->commentsInboxReadTillId; - const auto maxId = data.maxId ? data.maxId : views->commentsMaxId; - const auto countsChanged = (views->replies.count != count) - || (views->commentsInboxReadTillId != readTillId) - || (views->commentsMaxId != maxId); - const auto megagroupChanged = (views->commentsMegagroupId != channelId); - const auto recentChanged = (views->recentRepliers != repliers); - if (!countsChanged && !megagroupChanged && !recentChanged) { - return; - } - views->replies.count = count; - if (recentChanged) { - views->recentRepliers = repliers; - } - const auto wasUnread = areCommentsUnread(); - views->commentsMegagroupId = channelId; - views->commentsInboxReadTillId = readTillId; - views->commentsMaxId = maxId; - if (wasUnread != areCommentsUnread()) { - history()->owner().requestItemRepaint(this); - } - refreshRepliesText(views, megagroupChanged); -} - -void HistoryMessage::clearReplies() { - auto views = Get(); - if (!views) { - return; - } - const auto viewsPart = views->views; - if (viewsPart.count < 0) { - RemoveComponents(HistoryMessageViews::Bit()); - } else { - *views = HistoryMessageViews(); - views->views = viewsPart; - } - history()->owner().requestItemResize(this); -} - -void HistoryMessage::refreshRepliesText( - not_null views, - bool forceResize) { - if (views->commentsMegagroupId) { - views->replies.text = (views->replies.count > 0) - ? tr::lng_comments_open_count( - tr::now, - lt_count_short, - views->replies.count) - : tr::lng_comments_open_none(tr::now); - views->replies.textWidth = st::semiboldFont->width( - views->replies.text); - views->repliesSmall.text = (views->replies.count > 0) - ? Lang::FormatCountToShort(views->replies.count).string - : QString(); - const auto hadText = (views->repliesSmall.textWidth > 0); - views->repliesSmall.textWidth = (views->replies.count > 0) - ? st::semiboldFont->width(views->repliesSmall.text) - : 0; - const auto hasText = (views->repliesSmall.textWidth > 0); - if (hasText != hadText) { - forceResize = true; - } - } - if (forceResize) { - history()->owner().requestItemResize(this); - } else { - history()->owner().requestItemRepaint(this); - } -} - -void HistoryMessage::changeRepliesCount(int delta, PeerId replier) { - const auto views = Get(); - const auto limit = HistoryMessageViews::kMaxRecentRepliers; - if (!views) { - return; - } - - // Update full count. - if (views->replies.count < 0) { - return; - } - views->replies.count = std::max(views->replies.count + delta, 0); - if (replier && views->commentsMegagroupId) { - if (delta < 0) { - views->recentRepliers.erase( - ranges::remove(views->recentRepliers, replier), - end(views->recentRepliers)); - } else if (!ranges::contains(views->recentRepliers, replier)) { - views->recentRepliers.insert(views->recentRepliers.begin(), replier); - while (views->recentRepliers.size() > limit) { - views->recentRepliers.pop_back(); - } - } - } - refreshRepliesText(views); - history()->owner().notifyItemDataChange(this); -} - -void HistoryMessage::setSponsoredFrom(const Data::SponsoredFrom &from) { - AddComponents(HistoryMessageSponsored::Bit()); - const auto sponsored = Get(); - sponsored->sender = std::make_unique( - from.title, - false); - sponsored->recommended = from.isRecommended; - sponsored->isForceUserpicDisplay = from.isForceUserpicDisplay; - if (from.userpic.location.valid()) { - sponsored->sender->customUserpic.set( - &history()->session(), - from.userpic); - } - - using Type = HistoryMessageSponsored::Type; - sponsored->type = from.isExactPost - ? Type::Post - : from.isBot - ? Type::Bot - : from.isBroadcast - ? Type::Broadcast - : (from.peer && from.peer->isUser()) - ? Type::User - : Type::Group; -} - -void HistoryMessage::setReplyFields( - MsgId replyTo, - MsgId replyToTop, - bool isForumPost) { - const auto reply = Get(); - if (!reply || isScheduled()) { - return; - } - reply->topicPost = isForumPost; - if ((reply->replyToMsgId != replyTo) - && !IsServerMsgId(reply->replyToMsgId)) { - reply->replyToMsgId = replyTo; - if (!reply->updateData(this)) { - RequestDependentMessageData( - this, - reply->replyToPeerId, - reply->replyToMsgId); - } - } - if ((reply->replyToMsgTop != replyToTop) - && !IsServerMsgId(reply->replyToMsgTop)) { - reply->replyToMsgTop = replyToTop; - changeReplyToTopCounter(reply, 1); - if (const auto topic = this->topic()) { - topic->maybeSetLastMessage(this); - } - } -} - -void HistoryMessage::setRealId(MsgId newId) { - HistoryItem::setRealId(newId); - - history()->owner().groups().refreshMessage(this); - history()->owner().requestItemResize(this); - if (const auto reply = Get()) { - if (reply->replyToLink()) { - reply->setReplyToLinkFrom(this); - } - changeReplyToTopCounter(reply, 1); - } -} - -void HistoryMessage::incrementReplyToTopCounter() { - if (const auto reply = Get()) { - changeReplyToTopCounter(reply, 1); - } -} - -void HistoryMessage::changeReplyToTopCounter( - not_null reply, - int delta) { - if (!isRegular() || !_history->peer->isMegagroup()) { - return; - } else if (delta > 0) { - _history->session().changes().messageUpdated( - this, - Data::MessageUpdate::Flag::ReplyToTopAdded); - } - const auto topId = reply->replyToTop(); - if (!topId) { - return; - } - const auto top = _history->owner().message(_history->peer->id, topId); - if (!top) { - return; - } - const auto from = displayFrom(); - const auto replier = from ? from->id : PeerId(); - top->changeRepliesCount(delta, replier); - if (const auto original = top->lookupDiscussionPostOriginal()) { - original->changeRepliesCount(delta, replier); - } -} - -void HistoryMessage::dependencyItemRemoved(HistoryItem *dependency) { - if (const auto reply = Get()) { - const auto documentId = reply->replyToDocumentId; - reply->itemRemoved(this, dependency); - if (documentId != reply->replyToDocumentId - && generateLocalEntitiesByReply()) { - history()->owner().requestItemTextRefresh(this); - } - } -} - -QString HistoryMessage::notificationHeader() const { - if (out() && isFromScheduled() && !_history->peer->isSelf()) { - return tr::lng_from_you(tr::now); - } else if (!_history->peer->isUser() && !isPost()) { - return from()->name(); - } - return QString(); -} - -std::unique_ptr HistoryMessage::createView( - not_null delegate, - HistoryView::Element *replacing) { - return delegate->elementCreate(this, replacing); -} - -HistoryMessage::~HistoryMessage() { - _media.reset(); - clearSavedMedia(); - if (auto reply = Get()) { - reply->clearData(this); - } -} diff --git a/Telegram/SourceFiles/history/history_message.h b/Telegram/SourceFiles/history/history_message.h deleted file mode 100644 index 37402441f..000000000 --- a/Telegram/SourceFiles/history/history_message.h +++ /dev/null @@ -1,276 +0,0 @@ -/* -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 -*/ -#pragma once - -#include "history/history_item.h" - -namespace Api { -struct SendAction; -struct SendOptions; -} // namespace Api - -namespace Data { -class Thread; -struct SponsoredFrom; -} // namespace Data - -namespace HistoryView { -class Message; -} // namespace HistoryView - -struct HistoryMessageEdited; -struct HistoryMessageReply; -struct HistoryMessageViews; -struct HistoryMessageMarkupData; - -void RequestDependentMessageData( - not_null item, - PeerId peerId, - MsgId msgId); -[[nodiscard]] MessageFlags NewMessageFlags(not_null peer); -[[nodiscard]] bool ShouldSendSilent( - not_null peer, - const Api::SendOptions &options); -[[nodiscard]] MsgId LookupReplyToTop( - not_null history, - MsgId replyToId); -[[nodiscard]] MTPMessageReplyHeader NewMessageReplyHeader( - const Api::SendAction &action); - -struct SendingErrorRequest { - MsgId topicRootId = 0; - const HistoryItemsList *forward = nullptr; - const TextWithTags *text = nullptr; - bool ignoreSlowmodeCountdown = false; -}; -[[nodiscard]] QString GetErrorTextForSending( - not_null peer, - SendingErrorRequest request); -[[nodiscard]] QString GetErrorTextForSending( - not_null thread, - SendingErrorRequest request); - -[[nodiscard]] TextWithEntities DropCustomEmoji(TextWithEntities text); - -class HistoryMessage final : public HistoryItem { -public: - HistoryMessage( - not_null history, - MsgId id, - const MTPDmessage &data, - MessageFlags localFlags); - HistoryMessage( - not_null history, - MsgId id, - const MTPDmessageService &data, - MessageFlags localFlags); - HistoryMessage( - not_null history, - MsgId id, - MessageFlags flags, - TimeId date, - PeerId from, - const QString &postAuthor, - not_null original, - MsgId topicRootId); // local forwarded - HistoryMessage( - not_null history, - MsgId id, - MessageFlags flags, - MsgId replyTo, - UserId viaBotId, - TimeId date, - PeerId from, - const QString &postAuthor, - const TextWithEntities &textWithEntities, - const MTPMessageMedia &media, - HistoryMessageMarkupData &&markup, - uint64 groupedId); // local message - HistoryMessage( - not_null history, - MsgId id, - MessageFlags flags, - MsgId replyTo, - UserId viaBotId, - TimeId date, - PeerId from, - const QString &postAuthor, - not_null document, - const TextWithEntities &caption, - HistoryMessageMarkupData &&markup); // local document - HistoryMessage( - not_null history, - MsgId id, - MessageFlags flags, - MsgId replyTo, - UserId viaBotId, - TimeId date, - PeerId from, - const QString &postAuthor, - not_null photo, - const TextWithEntities &caption, - HistoryMessageMarkupData &&markup); // local photo - HistoryMessage( - not_null history, - MsgId id, - MessageFlags flags, - MsgId replyTo, - UserId viaBotId, - TimeId date, - PeerId from, - const QString &postAuthor, - not_null game, - HistoryMessageMarkupData &&markup); // local game - HistoryMessage( - not_null history, - MsgId id, - Data::SponsoredFrom from, - const TextWithEntities &textWithEntities, - HistoryItem *injectedAfter); // sponsored - - void refreshMedia(const MTPMessageMedia *media); - void refreshSentMedia(const MTPMessageMedia *media); - void returnSavedMedia() override; - void setMedia(const MTPMessageMedia &media); - void checkBuyButton() override; - [[nodiscard]] static std::unique_ptr CreateMedia( - not_null item, - const MTPMessageMedia &media); - - [[nodiscard]] bool allowsForward() const override; - [[nodiscard]] bool allowsSendNow() const override; - [[nodiscard]] bool allowsEdit(TimeId now) const override; - - bool changeViewsCount(int count) override; - void setForwardsCount(int count) override; - void setReplies(HistoryMessageRepliesData &&data) override; - void clearReplies() override; - void changeRepliesCount(int delta, PeerId replier) override; - void setReplyFields( - MsgId replyTo, - MsgId replyToTop, - bool isForumPost) override; - void setPostAuthor(const QString &author) override; - void setRealId(MsgId newId) override; - void incrementReplyToTopCounter() override; - - void dependencyItemRemoved(HistoryItem *dependency) override; - - [[nodiscard]] QString notificationHeader() const override; - - // Looks on: - // f_edit_hide - // f_edit_date - // f_entities - // f_reply_markup - // f_media - // f_views - // f_forwards - // f_replies - // f_ttl_period - void applyEdition(HistoryMessageEdition &&edition) override; - - void applyEdition(const MTPDmessageService &message) override; - void applyEdition(const MTPMessageExtendedMedia &media) override; - void updateSentContent( - const TextWithEntities &textWithEntities, - const MTPMessageMedia *media) override; - void updateReplyMarkup(HistoryMessageMarkupData &&markup) override; - void updateForwardedInfo(const MTPMessageFwdHeader *fwd) override; - void contributeToSlowmode(TimeId realDate = 0) override; - - void addToUnreadThings(HistoryUnreadThings::AddType type) override; - void destroyHistoryEntry() override; - [[nodiscard]] Storage::SharedMediaTypesMask sharedMediaTypes() const override; - - [[nodiscard]] MsgId replyToId() const override; - [[nodiscard]] MsgId replyToTop() const override; - [[nodiscard]] MsgId topicRootId() const override; - - void setText(const TextWithEntities &textWithEntities) override; - [[nodiscard]] TextWithEntities originalText() const override; - [[nodiscard]] auto originalTextWithLocalEntities() const - -> TextWithEntities override; - [[nodiscard]] TextForMimeData clipboardText() const override; - - [[nodiscard]] int viewsCount() const override; - [[nodiscard]] int repliesCount() const override; - [[nodiscard]] bool repliesAreComments() const override; - [[nodiscard]] bool externalReply() const override; - - void setCommentsInboxReadTill(MsgId readTillId) override; - void setCommentsMaxId(MsgId maxId) override; - void setCommentsPossibleMaxId(MsgId possibleMaxId) override; - [[nodiscard]] bool areCommentsUnread() const override; - - [[nodiscard]] FullMsgId commentsItemId() const override; - void setCommentsItemId(FullMsgId id) override; - bool updateDependencyItem() override; - [[nodiscard]] MsgId dependencyMsgId() const override { - return replyToId(); - } - - void applySentMessage(const MTPDmessage &data) override; - void applySentMessage( - const QString &text, - const MTPDupdateShortSentMessage &data, - bool wasAlready) override; - - [[nodiscard]] std::unique_ptr createView( - not_null delegate, - HistoryView::Element *replacing = nullptr) override; - - ~HistoryMessage(); - -private: - void setTextValue(TextWithEntities text); - [[nodiscard]] bool isTooOldForEdit(TimeId now) const; - [[nodiscard]] bool isLegacyMessage() const { - return _flags & MessageFlag::Legacy; - } - - [[nodiscard]] bool checkCommentsLinkedChat(ChannelId id) const; - - // For an invoice button we replace the button text with a "Receipt" key. - // It should show the receipt for the payed invoice. Still let mobile apps do that. - void replaceBuyWithReceiptInMarkup(); - - void setReplyMarkup(HistoryMessageMarkupData &&markup); - - struct CreateConfig; - void createComponentsHelper( - MessageFlags flags, - MsgId replyTo, - UserId viaBotId, - const QString &postAuthor, - HistoryMessageMarkupData &&markup); - void createComponents(CreateConfig &&config); - void setupForwardedComponent(const CreateConfig &config); - void changeReplyToTopCounter( - not_null reply, - int delta); - void refreshRepliesText( - not_null views, - bool forceResize = false); - void setSponsoredFrom(const Data::SponsoredFrom &from); - - static void FillForwardedInfo( - CreateConfig &config, - const MTPDmessageFwdHeader &data); - - [[nodiscard]] bool generateLocalEntitiesByReply() const; - [[nodiscard]] TextWithEntities withLocalEntities( - const TextWithEntities &textWithEntities) const; - - [[nodiscard]] bool checkRepliesPts( - const HistoryMessageRepliesData &data) const; - - friend class HistoryView::Element; - friend class HistoryView::Message; - -}; diff --git a/Telegram/SourceFiles/history/history_service.cpp b/Telegram/SourceFiles/history/history_service.cpp deleted file mode 100644 index 9d9ea2d3b..000000000 --- a/Telegram/SourceFiles/history/history_service.cpp +++ /dev/null @@ -1,1821 +0,0 @@ -/* -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 "history/history_service.h" - -#include "chat_helpers/stickers_gift_box_pack.h" -#include "lang/lang_keys.h" -#include "mainwidget.h" -#include "main/main_session.h" -#include "main/main_domain.h" // Core::App().domain().activate(). -#include "menu/menu_ttl_validator.h" -#include "apiwrap.h" -#include "history/history.h" -#include "history/view/media/history_view_invoice.h" -#include "history/history_message.h" -#include "history/history_item_components.h" -#include "history/view/history_view_service_message.h" -#include "history/view/history_view_item_preview.h" -#include "data/data_folder.h" -#include "data/data_forum.h" -#include "data/data_forum_topic.h" -#include "data/data_session.h" -#include "data/data_media_types.h" -#include "data/data_game.h" -#include "data/data_channel.h" -#include "data/data_user.h" -#include "data/data_chat.h" -#include "data/data_changes.h" -#include "data/data_group_call.h" // Data::GroupCall::id(). -#include "data/stickers/data_custom_emoji.h" -#include "core/application.h" -#include "core/click_handler_types.h" -#include "base/unixtime.h" -#include "base/timer_rpl.h" -#include "calls/calls_instance.h" // Core::App().calls().joinGroupCall. -#include "window/notifications_manager.h" -#include "window/window_controller.h" -#include "window/window_session_controller.h" -#include "storage/storage_shared_media.h" -#include "payments/payments_checkout_process.h" // CheckoutProcess::Start. -#include "ui/text/format_values.h" -#include "ui/text/text_utilities.h" - -namespace { - -constexpr auto kPinnedMessageTextLimit = 16; - -using ItemPreview = HistoryView::ItemPreview; - -[[nodiscard]] bool PeerCallKnown(not_null peer) { - if (peer->groupCall() != nullptr) { - return true; - } else if (const auto chat = peer->asChat()) { - return !(chat->flags() & ChatDataFlag::CallActive); - } else if (const auto channel = peer->asChannel()) { - return !(channel->flags() & ChannelDataFlag::CallActive); - } - return true; -} - -[[nodiscard]] rpl::producer PeerHasThisCallValue( - not_null peer, - CallId id) { - return peer->session().changes().peerFlagsValue( - peer, - Data::PeerUpdate::Flag::GroupCall - ) | rpl::filter([=] { - return PeerCallKnown(peer); - }) | rpl::map([=] { - const auto call = peer->groupCall(); - return (call && call->id() == id); - }) | rpl::distinct_until_changed( - ) | rpl::take_while([=](bool hasThisCall) { - return hasThisCall; - }) | rpl::then( - rpl::single(false) - ); -} - -[[nodiscard]] CallId CallIdFromInput(const MTPInputGroupCall &data) { - return data.match([&](const MTPDinputGroupCall &data) { - return data.vid().v; - }); -} - -[[nodiscard]] ClickHandlerPtr GroupCallClickHandler( - not_null peer, - CallId callId) { - return std::make_shared([=] { - const auto call = peer->groupCall(); - if (call && call->id() == callId) { - const auto &windows = peer->session().windows(); - if (windows.empty()) { - Core::App().domain().activate(&peer->session().account()); - if (windows.empty()) { - return; - } - } - windows.front()->startOrJoinGroupCall(peer, {}); - } - }); -} - -} // namespace - -void HistoryService::setMessageByAction(const MTPmessageAction &action) { - auto prepareChatAddUserText = [this](const MTPDmessageActionChatAddUser &action) { - auto result = PreparedText{}; - auto &users = action.vusers().v; - if (users.size() == 1) { - auto u = history()->owner().user(users[0].v); - if (u == _from) { - result.links.push_back(fromLink()); - result.text = tr::lng_action_user_joined( - tr::now, - lt_from, - fromLinkText(), // Link 1. - Ui::Text::WithEntities); - } else { - result.links.push_back(fromLink()); - result.links.push_back(u->createOpenLink()); - result.text = tr::lng_action_add_user( - tr::now, - lt_from, - fromLinkText(), // Link 1. - lt_user, - Ui::Text::Link(u->name(), 2), // Link 2. - Ui::Text::WithEntities); - } - } else if (users.isEmpty()) { - result.links.push_back(fromLink()); - result.text = tr::lng_action_add_user( - tr::now, - lt_from, - fromLinkText(), // Link 1. - lt_user, - { .text = u"somebody"_q }, - Ui::Text::WithEntities); - } else { - result.links.push_back(fromLink()); - for (auto i = 0, l = int(users.size()); i != l; ++i) { - auto user = history()->owner().user(users[i].v); - result.links.push_back(user->createOpenLink()); - - auto linkText = Ui::Text::Link(user->name(), 2 + i); - if (i == 0) { - result.text = linkText; - } else if (i + 1 == l) { - result.text = tr::lng_action_add_users_and_last( - tr::now, - lt_accumulated, - result.text, - lt_user, - linkText, - Ui::Text::WithEntities); - } else { - result.text = tr::lng_action_add_users_and_one( - tr::now, - lt_accumulated, - result.text, - lt_user, - linkText, - Ui::Text::WithEntities); - } - } - result.text = tr::lng_action_add_users_many( - tr::now, - lt_from, - fromLinkText(), // Link 1. - lt_users, - result.text, - Ui::Text::WithEntities); - } - return result; - }; - - auto prepareChatJoinedByLink = [this](const MTPDmessageActionChatJoinedByLink &action) { - auto result = PreparedText{}; - result.links.push_back(fromLink()); - result.text = tr::lng_action_user_joined_by_link( - tr::now, - lt_from, - fromLinkText(), // Link 1. - Ui::Text::WithEntities); - return result; - }; - - auto prepareChatCreate = [this](const MTPDmessageActionChatCreate &action) { - auto result = PreparedText{}; - result.links.push_back(fromLink()); - result.text = tr::lng_action_created_chat( - tr::now, - lt_from, - fromLinkText(), // Link 1. - lt_title, - { .text = qs(action.vtitle()) }, - Ui::Text::WithEntities); - return result; - }; - - auto prepareChannelCreate = [this](const MTPDmessageActionChannelCreate &action) { - auto result = PreparedText {}; - if (isPost()) { - result.text = tr::lng_action_created_channel( - tr::now, - Ui::Text::WithEntities); - } else { - result.links.push_back(fromLink()); - result.text = tr::lng_action_created_chat( - tr::now, - lt_from, - fromLinkText(), // Link 1. - lt_title, - { .text = qs(action.vtitle()) }, - Ui::Text::WithEntities); - } - return result; - }; - - auto prepareChatDeletePhoto = [this] { - auto result = PreparedText{}; - if (isPost()) { - result.text = tr::lng_action_removed_photo_channel( - tr::now, - Ui::Text::WithEntities); - } else { - result.links.push_back(fromLink()); - result.text = tr::lng_action_removed_photo( - tr::now, - lt_from, - fromLinkText(), // Link 1. - Ui::Text::WithEntities); - } - return result; - }; - - auto prepareChatDeleteUser = [this](const MTPDmessageActionChatDeleteUser &action) { - auto result = PreparedText{}; - if (peerFromUser(action.vuser_id()) == _from->id) { - result.links.push_back(fromLink()); - result.text = tr::lng_action_user_left( - tr::now, - lt_from, - fromLinkText(), // Link 1. - Ui::Text::WithEntities); - } else { - auto user = history()->owner().user(action.vuser_id().v); - result.links.push_back(fromLink()); - result.links.push_back(user->createOpenLink()); - result.text = tr::lng_action_kick_user( - tr::now, - lt_from, - fromLinkText(), // Link 1. - lt_user, - Ui::Text::Link(user->name(), 2), // Link 2. - Ui::Text::WithEntities); - } - return result; - }; - - auto prepareChatEditPhoto = [this](const MTPDmessageActionChatEditPhoto &action) { - auto result = PreparedText{}; - if (isPost()) { - result.text = tr::lng_action_changed_photo_channel( - tr::now, - Ui::Text::WithEntities); - } else { - result.links.push_back(fromLink()); - result.text = tr::lng_action_changed_photo( - tr::now, - lt_from, - fromLinkText(), // Link 1. - Ui::Text::WithEntities); - } - return result; - }; - - auto prepareChatEditTitle = [this](const MTPDmessageActionChatEditTitle &action) { - auto result = PreparedText{}; - if (isPost()) { - result.text = tr::lng_action_changed_title_channel( - tr::now, - lt_title, - { .text = (qs(action.vtitle())) }, - Ui::Text::WithEntities); - } else { - result.links.push_back(fromLink()); - result.text = tr::lng_action_changed_title( - tr::now, - lt_from, - fromLinkText(), // Link 1. - lt_title, - { .text = qs(action.vtitle()) }, - Ui::Text::WithEntities); - } - return result; - }; - - auto prepareScreenshotTaken = [this] { - auto result = PreparedText{}; - if (out()) { - result.text = tr::lng_action_you_took_screenshot( - tr::now, - Ui::Text::WithEntities); - } else { - result.links.push_back(fromLink()); - result.text = tr::lng_action_took_screenshot( - tr::now, - lt_from, - fromLinkText(), // Link 1. - Ui::Text::WithEntities); - } - return result; - }; - - auto prepareCustomAction = [&](const MTPDmessageActionCustomAction &action) { - auto result = PreparedText{}; - result.text = { .text = qs(action.vmessage()) }; - return result; - }; - - auto prepareBotAllowed = [&](const MTPDmessageActionBotAllowed &action) { - auto result = PreparedText{}; - const auto domain = qs(action.vdomain()); - result.text = tr::lng_action_bot_allowed_from_domain( - tr::now, - lt_domain, - Ui::Text::Link(domain, u"http://"_q + domain), - Ui::Text::WithEntities); - return result; - }; - - auto prepareSecureValuesSent = [&](const MTPDmessageActionSecureValuesSent &action) { - auto result = PreparedText{}; - auto documents = QStringList(); - for (const auto &type : action.vtypes().v) { - documents.push_back([&] { - switch (type.type()) { - case mtpc_secureValueTypePersonalDetails: - return tr::lng_action_secure_personal_details(tr::now); - case mtpc_secureValueTypePassport: - case mtpc_secureValueTypeDriverLicense: - case mtpc_secureValueTypeIdentityCard: - case mtpc_secureValueTypeInternalPassport: - return tr::lng_action_secure_proof_of_identity(tr::now); - case mtpc_secureValueTypeAddress: - return tr::lng_action_secure_address(tr::now); - case mtpc_secureValueTypeUtilityBill: - case mtpc_secureValueTypeBankStatement: - case mtpc_secureValueTypeRentalAgreement: - case mtpc_secureValueTypePassportRegistration: - case mtpc_secureValueTypeTemporaryRegistration: - return tr::lng_action_secure_proof_of_address(tr::now); - case mtpc_secureValueTypePhone: - return tr::lng_action_secure_phone(tr::now); - case mtpc_secureValueTypeEmail: - return tr::lng_action_secure_email(tr::now); - } - Unexpected("Type in prepareSecureValuesSent."); - }()); - }; - result.links.push_back(history()->peer->createOpenLink()); - result.text = tr::lng_action_secure_values_sent( - tr::now, - lt_user, - Ui::Text::Link(history()->peer->name(), QString()), // Link 1. - lt_documents, - { .text = documents.join(", ") }, - Ui::Text::WithEntities); - return result; - }; - - auto prepareContactSignUp = [this] { - auto result = PreparedText{}; - result.links.push_back(fromLink()); - result.text = tr::lng_action_user_registered( - tr::now, - lt_from, - fromLinkText(), // Link 1. - Ui::Text::WithEntities); - return result; - }; - - auto prepareProximityReached = [this](const MTPDmessageActionGeoProximityReached &action) { - auto result = PreparedText{}; - const auto fromId = peerFromMTP(action.vfrom_id()); - const auto fromPeer = history()->owner().peer(fromId); - const auto toId = peerFromMTP(action.vto_id()); - const auto toPeer = history()->owner().peer(toId); - const auto selfId = _from->session().userPeerId(); - const auto distanceMeters = action.vdistance().v; - const auto distance = [&] { - if (distanceMeters >= 1000) { - const auto km = (10 * (distanceMeters / 10)) / 1000.; - return tr::lng_action_proximity_distance_km( - tr::now, - lt_count, - km); - } else { - return tr::lng_action_proximity_distance_m( - tr::now, - lt_count, - distanceMeters); - } - }(); - result.text = [&] { - if (fromId == selfId) { - result.links.push_back(toPeer->createOpenLink()); - return tr::lng_action_you_proximity_reached( - tr::now, - lt_distance, - { .text = distance }, - lt_user, - Ui::Text::Link(toPeer->name(), QString()), // Link 1. - Ui::Text::WithEntities); - } else if (toId == selfId) { - result.links.push_back(fromPeer->createOpenLink()); - return tr::lng_action_proximity_reached_you( - tr::now, - lt_from, - Ui::Text::Link(fromPeer->name(), QString()), // Link 1. - lt_distance, - { .text = distance }, - Ui::Text::WithEntities); - } else { - result.links.push_back(fromPeer->createOpenLink()); - result.links.push_back(toPeer->createOpenLink()); - return tr::lng_action_proximity_reached( - tr::now, - lt_from, - Ui::Text::Link(fromPeer->name(), 1), // Link 1. - lt_distance, - { .text = distance }, - lt_user, - Ui::Text::Link(toPeer->name(), 2), // Link 2. - Ui::Text::WithEntities); - } - }(); - return result; - }; - - auto prepareGroupCall = [this](const MTPDmessageActionGroupCall &action) { - auto result = PreparedText{}; - if (const auto duration = action.vduration()) { - const auto seconds = duration->v; - const auto days = seconds / 86400; - const auto hours = seconds / 3600; - const auto minutes = seconds / 60; - auto text = (days > 1) - ? tr::lng_days(tr::now, lt_count, days) - : (hours > 1) - ? tr::lng_hours(tr::now, lt_count, hours) - : (minutes > 1) - ? tr::lng_minutes(tr::now, lt_count, minutes) - : tr::lng_seconds(tr::now, lt_count, seconds); - if (history()->peer->isBroadcast()) { - result.text = tr::lng_action_group_call_finished( - tr::now, - lt_duration, - { .text = text }, - Ui::Text::WithEntities); - } else { - result.links.push_back(fromLink()); - result.text = tr::lng_action_group_call_finished_group( - tr::now, - lt_from, - fromLinkText(), // Link 1. - lt_duration, - { .text = text }, - Ui::Text::WithEntities); - } - return result; - } - if (history()->peer->isBroadcast()) { - result.text = tr::lng_action_group_call_started_channel( - tr::now, - Ui::Text::WithEntities); - } else { - result.links.push_back(fromLink()); - result.text = tr::lng_action_group_call_started_group( - tr::now, - lt_from, - fromLinkText(), // Link 1. - Ui::Text::WithEntities); - } - return result; - }; - - auto prepareInviteToGroupCall = [this](const MTPDmessageActionInviteToGroupCall &action) { - const auto callId = CallIdFromInput(action.vcall()); - const auto owner = &history()->owner(); - const auto peer = history()->peer; - for (const auto &id : action.vusers().v) { - const auto user = owner->user(id.v); - if (callId) { - owner->registerInvitedToCallUser(callId, peer, user); - } - }; - const auto linkCallId = PeerHasThisCall(peer, callId).value_or(false) - ? callId - : 0; - return prepareInvitedToCallText(action.vusers().v, linkCallId); - }; - - auto prepareSetMessagesTTL = [this](const MTPDmessageActionSetMessagesTTL &action) { - auto result = PreparedText{}; - const auto period = action.vperiod().v; - const auto duration = (period == 5) - ? u"5 seconds"_q - : Ui::FormatTTL(period); - if (const auto from = action.vauto_setting_from()) { - if (const auto peer = _from->owner().peer(peerFromUser(*from))) { - if (!peer->isSelf() && period) { - result.text = tr::lng_action_ttl_global( - tr::now, - lt_from, - fromLinkText(), // Link 1. - lt_duration, - { .text = duration }, - Ui::Text::WithEntities); - return result; - } - } - } - if (isPost()) { - if (!period) { - result.text = tr::lng_action_ttl_removed_channel( - tr::now, - Ui::Text::WithEntities); - } else { - result.text = tr::lng_action_ttl_changed_channel( - tr::now, - lt_duration, - { .text = duration }, - Ui::Text::WithEntities); - } - } else if (_from->isSelf()) { - if (!period) { - result.text = tr::lng_action_ttl_removed_you( - tr::now, - Ui::Text::WithEntities); - } else { - result.text = tr::lng_action_ttl_changed_you( - tr::now, - lt_duration, - { .text = duration }, - Ui::Text::WithEntities); - } - } else { - result.links.push_back(fromLink()); - if (!period) { - result.text = tr::lng_action_ttl_removed( - tr::now, - lt_from, - fromLinkText(), // Link 1. - Ui::Text::WithEntities); - } else { - result.text = tr::lng_action_ttl_changed( - tr::now, - lt_from, - fromLinkText(), // Link 1. - lt_duration, - { .text = duration }, - Ui::Text::WithEntities); - } - } - return result; - }; - - auto prepareSetChatTheme = [this](const MTPDmessageActionSetChatTheme &action) { - auto result = PreparedText{}; - const auto text = qs(action.vemoticon()); - if (!text.isEmpty()) { - if (_from->isSelf()) { - result.text = tr::lng_action_you_theme_changed( - tr::now, - lt_emoji, - { .text = text }, - Ui::Text::WithEntities); - } else { - result.links.push_back(fromLink()); - result.text = tr::lng_action_theme_changed( - tr::now, - lt_from, - fromLinkText(), // Link 1. - lt_emoji, - { .text = text }, - Ui::Text::WithEntities); - } - } else { - if (_from->isSelf()) { - result.text = tr::lng_action_you_theme_disabled( - tr::now, - Ui::Text::WithEntities); - } else { - result.links.push_back(fromLink()); - result.text = tr::lng_action_theme_disabled( - tr::now, - lt_from, - fromLinkText(), // Link 1. - Ui::Text::WithEntities); - } - } - return result; - }; - - auto prepareChatJoinedByRequest = [this](const MTPDmessageActionChatJoinedByRequest &action) { - auto result = PreparedText{}; - result.links.push_back(fromLink()); - result.text = tr::lng_action_user_joined_by_request( - tr::now, - lt_from, - fromLinkText(), // Link 1. - Ui::Text::WithEntities); - return result; - }; - - auto prepareWebViewDataSent = [](const MTPDmessageActionWebViewDataSent &action) { - auto result = PreparedText{}; - result.text = tr::lng_action_webview_data_done( - tr::now, - lt_text, - { .text = qs(action.vtext()) }, - Ui::Text::WithEntities); - return result; - }; - - auto prepareGiftPremium = [&]( - const MTPDmessageActionGiftPremium &action) { - auto result = PreparedText{}; - const auto isSelf = (_from->id == _from->session().userPeerId()); - const auto peer = isSelf ? history()->peer : _from; - history()->session().giftBoxStickersPacks().load(); - const auto amount = action.vamount().v; - const auto currency = qs(action.vcurrency()); - result.links.push_back(peer->createOpenLink()); - result.text = (isSelf - ? tr::lng_action_gift_received_me - : tr::lng_action_gift_received)( - tr::now, - lt_user, - Ui::Text::Link(peer->name(), 1), // Link 1. - lt_cost, - { Ui::FillAmountAndCurrency(amount, currency) }, - Ui::Text::WithEntities); - return result; - }; - - auto prepareTopicCreate = [&](const MTPDmessageActionTopicCreate &action) { - auto result = PreparedText{}; - const auto topicUrl = u"internal:url:https://t.me/c/%1/%2"_q - .arg(peerToChannel(history()->peer->id).bare) - .arg(id.bare); - result.text = tr::lng_action_topic_created( - tr::now, - lt_topic, - Ui::Text::Link( - Data::ForumTopicIconWithTitle( - id, - action.vicon_emoji_id().value_or_empty(), - qs(action.vtitle())), - topicUrl), - Ui::Text::WithEntities); - return result; - }; - - auto prepareTopicEdit = [&](const MTPDmessageActionTopicEdit &action) { - auto result = PreparedText{}; - if (const auto closed = action.vclosed()) { - result.text = { mtpIsTrue(*closed) - ? tr::lng_action_topic_closed_inside(tr::now) - : tr::lng_action_topic_reopened_inside(tr::now) }; - } else if (const auto hidden = action.vhidden()) { - result.text = { mtpIsTrue(*hidden) - ? tr::lng_action_topic_hidden_inside(tr::now) - : tr::lng_action_topic_unhidden_inside(tr::now) }; - } else if (!action.vtitle()) { - if (const auto icon = action.vicon_emoji_id()) { - if (const auto iconId = icon->v) { - result.links.push_back(fromLink()); - result.text = tr::lng_action_topic_icon_changed( - tr::now, - lt_from, - fromLinkText(), // Link 1. - lt_link, - { tr::lng_action_topic_placeholder(tr::now) }, - lt_emoji, - Data::SingleCustomEmoji(iconId), - Ui::Text::WithEntities); - } else { - result.links.push_back(fromLink()); - result.text = tr::lng_action_topic_icon_removed( - tr::now, - lt_from, - fromLinkText(), // Link 1. - lt_link, - { tr::lng_action_topic_placeholder(tr::now) }, - Ui::Text::WithEntities); - } - } - } else { - result.links.push_back(fromLink()); - result.text = tr::lng_action_topic_renamed( - tr::now, - lt_from, - fromLinkText(), // Link 1. - lt_link, - { tr::lng_action_topic_placeholder(tr::now) }, - lt_title, - Data::ForumTopicIconWithTitle( - topicRootId(), - action.vicon_emoji_id().value_or_empty(), - qs(*action.vtitle())), - Ui::Text::WithEntities); - } - if (result.text.empty()) { - result.text = { tr::lng_message_empty(tr::now) }; - } - return result; - }; - - setServiceText(action.match([&]( - const MTPDmessageActionChatAddUser &data) { - return prepareChatAddUserText(data); - }, [&](const MTPDmessageActionChatJoinedByLink &data) { - return prepareChatJoinedByLink(data); - }, [&](const MTPDmessageActionChatCreate &data) { - return prepareChatCreate(data); - }, [](const MTPDmessageActionChatMigrateTo &) { - return PreparedText(); - }, [](const MTPDmessageActionChannelMigrateFrom &) { - return PreparedText(); - }, [](const MTPDmessageActionHistoryClear &) { - return PreparedText(); - }, [&](const MTPDmessageActionChannelCreate &data) { - return prepareChannelCreate(data); - }, [&](const MTPDmessageActionChatDeletePhoto &) { - return prepareChatDeletePhoto(); - }, [&](const MTPDmessageActionChatDeleteUser &data) { - return prepareChatDeleteUser(data); - }, [&](const MTPDmessageActionChatEditPhoto &data) { - return prepareChatEditPhoto(data); - }, [&](const MTPDmessageActionChatEditTitle &data) { - return prepareChatEditTitle(data); - }, [&](const MTPDmessageActionPinMessage &) { - return preparePinnedText(); - }, [&](const MTPDmessageActionGameScore &) { - return prepareGameScoreText(); - }, [&](const MTPDmessageActionPhoneCall &) -> PreparedText { - Unexpected("PhoneCall type in HistoryService."); - }, [&](const MTPDmessageActionPaymentSent &) { - return preparePaymentSentText(); - }, [&](const MTPDmessageActionScreenshotTaken &) { - return prepareScreenshotTaken(); - }, [&](const MTPDmessageActionCustomAction &data) { - return prepareCustomAction(data); - }, [&](const MTPDmessageActionBotAllowed &data) { - return prepareBotAllowed(data); - }, [&](const MTPDmessageActionSecureValuesSent &data) { - return prepareSecureValuesSent(data); - }, [&](const MTPDmessageActionContactSignUp &data) { - return prepareContactSignUp(); - }, [&](const MTPDmessageActionGeoProximityReached &data) { - return prepareProximityReached(data); - }, [](const MTPDmessageActionPaymentSentMe &) { - LOG(("API Error: messageActionPaymentSentMe received.")); - return PreparedText{ { tr::lng_message_empty(tr::now) } }; - }, [](const MTPDmessageActionSecureValuesSentMe &) { - LOG(("API Error: messageActionSecureValuesSentMe received.")); - return PreparedText{ { tr::lng_message_empty(tr::now) } }; - }, [&](const MTPDmessageActionGroupCall &data) { - return prepareGroupCall(data); - }, [&](const MTPDmessageActionInviteToGroupCall &data) { - return prepareInviteToGroupCall(data); - }, [&](const MTPDmessageActionSetMessagesTTL &data) { - return prepareSetMessagesTTL(data); - }, [&](const MTPDmessageActionGroupCallScheduled &data) { - return prepareCallScheduledText(data.vschedule_date().v); - }, [&](const MTPDmessageActionSetChatTheme &data) { - return prepareSetChatTheme(data); - }, [&](const MTPDmessageActionChatJoinedByRequest &data) { - return prepareChatJoinedByRequest(data); - }, [&](const MTPDmessageActionWebViewDataSent &data) { - return prepareWebViewDataSent(data); - }, [&](const MTPDmessageActionGiftPremium &data) { - return prepareGiftPremium(data); - }, [&](const MTPDmessageActionTopicCreate &data) { - return prepareTopicCreate(data); - }, [&](const MTPDmessageActionTopicEdit &data) { - return prepareTopicEdit(data); - }, [&](const MTPDmessageActionWebViewDataSentMe &data) { - LOG(("API Error: messageActionWebViewDataSentMe received.")); - return PreparedText{ { tr::lng_message_empty(tr::now) } }; - }, [](const MTPDmessageActionEmpty &) { - return PreparedText{ { tr::lng_message_empty(tr::now) } }; - })); - - // Additional information. - applyAction(action); -} - -void HistoryService::applyAction(const MTPMessageAction &action) { - action.match([&](const MTPDmessageActionChatAddUser &data) { - if (const auto channel = history()->peer->asMegagroup()) { - const auto selfUserId = history()->session().userId(); - for (const auto &item : data.vusers().v) { - if (peerFromUser(item) == selfUserId) { - channel->mgInfo->joinedMessageFound = true; - break; - } - } - } - }, [&](const MTPDmessageActionChatJoinedByLink &data) { - if (_from->isSelf()) { - if (const auto channel = history()->peer->asMegagroup()) { - channel->mgInfo->joinedMessageFound = true; - } - } - }, [&](const MTPDmessageActionChatEditPhoto &data) { - data.vphoto().match([&](const MTPDphoto &photo) { - _media = std::make_unique( - this, - history()->peer, - history()->owner().processPhoto(photo)); - }, [](const MTPDphotoEmpty &) { - }); - }, [&](const MTPDmessageActionChatCreate &) { - _flags |= MessageFlag::IsGroupEssential; - }, [&](const MTPDmessageActionChannelCreate &) { - _flags |= MessageFlag::IsGroupEssential; - }, [&](const MTPDmessageActionChatMigrateTo &) { - _flags |= MessageFlag::IsGroupEssential; - }, [&](const MTPDmessageActionChannelMigrateFrom &) { - _flags |= MessageFlag::IsGroupEssential; - }, [&](const MTPDmessageActionContactSignUp &) { - _flags |= MessageFlag::IsContactSignUp; - }, [&](const MTPDmessageActionChatJoinedByRequest &data) { - if (_from->isSelf()) { - if (const auto channel = history()->peer->asMegagroup()) { - channel->mgInfo->joinedMessageFound = true; - } - } - }, [&](const MTPDmessageActionGiftPremium &data) { - _media = std::make_unique( - this, - _from, - data.vmonths().v); - }, [](const auto &) { - }); -} - -void HistoryService::setSelfDestruct(HistoryServiceSelfDestruct::Type type, int ttlSeconds) { - UpdateComponents(HistoryServiceSelfDestruct::Bit()); - auto selfdestruct = Get(); - selfdestruct->timeToLive = ttlSeconds * 1000LL; - selfdestruct->type = type; -} - -bool HistoryService::updateDependent(bool force) { - auto dependent = GetDependentData(); - Assert(dependent != nullptr); - - if (!force) { - if (!dependent->msgId || dependent->msg) { - return true; - } - } - - if (!dependent->lnk) { - dependent->lnk = goToMessageClickHandler( - (dependent->peerId - ? history()->owner().peer(dependent->peerId) - : history()->peer), - dependent->msgId, - fullId()); - } - auto gotDependencyItem = false; - if (!dependent->msg) { - dependent->msg = history()->owner().message( - (dependent->peerId - ? dependent->peerId - : _history->peer->id), - dependent->msgId); - if (dependent->msg) { - if (dependent->msg->isEmpty()) { - // Really it is deleted. - dependent->msg = nullptr; - force = true; - } else { - history()->owner().registerDependentMessage( - this, - dependent->msg); - gotDependencyItem = true; - } - } - } - if (dependent->msg) { - updateDependentText(); - } else if (force) { - if (dependent->msgId > 0) { - dependent->msgId = 0; - gotDependencyItem = true; - } - updateDependentText(); - } - if (force && gotDependencyItem) { - Core::App().notifications().checkDelayed(); - } - return (dependent->msg || !dependent->msgId); -} - -HistoryService::PreparedText HistoryService::prepareInvitedToCallText( - const QVector &users, - CallId linkCallId) { - const auto owner = &history()->owner(); - auto chatText = tr::lng_action_invite_user_chat( - tr::now, - Ui::Text::WithEntities); - auto result = PreparedText{}; - result.links.push_back(fromLink()); - auto linkIndex = 1; - if (linkCallId) { - const auto peer = history()->peer; - result.links.push_back(GroupCallClickHandler(peer, linkCallId)); - chatText = Ui::Text::Link(chatText.text, ++linkIndex); - } - if (users.size() == 1) { - auto user = owner->user(users[0].v); - result.links.push_back(user->createOpenLink()); - result.text = tr::lng_action_invite_user( - tr::now, - lt_from, - fromLinkText(), // Link 1. - lt_user, - Ui::Text::Link(user->name(), ++linkIndex), // Link N. - lt_chat, - chatText, - Ui::Text::WithEntities); - } else if (users.isEmpty()) { - result.text = tr::lng_action_invite_user( - tr::now, - lt_from, - fromLinkText(), // Link 1. - lt_user, - { .text = u"somebody"_q }, - lt_chat, - chatText, - Ui::Text::WithEntities); - } else { - for (auto i = 0, l = int(users.size()); i != l; ++i) { - auto user = owner->user(users[i].v); - result.links.push_back(user->createOpenLink()); - - auto linkText = Ui::Text::Link(user->name(), ++linkIndex); - if (i == 0) { - result.text = linkText; - } else if (i + 1 == l) { - result.text = tr::lng_action_invite_users_and_last( - tr::now, - lt_accumulated, - result.text, - lt_user, - linkText, - Ui::Text::WithEntities); - } else { - result.text = tr::lng_action_invite_users_and_one( - tr::now, - lt_accumulated, - result.text, - lt_user, - linkText, - Ui::Text::WithEntities); - } - } - result.text = tr::lng_action_invite_users_many( - tr::now, - lt_from, - fromLinkText(), // Link 1. - lt_users, - result.text, - lt_chat, - chatText, - Ui::Text::WithEntities); - } - return result; -} - -HistoryService::PreparedText HistoryService::preparePinnedText() { - auto result = PreparedText {}; - auto pinned = Get(); - if (pinned && pinned->msg) { - const auto mediaText = [&] { - using TTL = HistoryServiceSelfDestruct; - if (const auto media = pinned->msg->media()) { - return media->pinnedTextSubstring(); - } else if (const auto selfdestruct = pinned->msg->Get()) { - if (selfdestruct->type == TTL::Type::Photo) { - return tr::lng_action_pinned_media_photo(tr::now); - } else if (selfdestruct->type == TTL::Type::Video) { - return tr::lng_action_pinned_media_video(tr::now); - } - } - return QString(); - }(); - result.links.push_back(fromLink()); - result.links.push_back(pinned->lnk); - if (mediaText.isEmpty()) { - auto original = pinned->msg->originalText(); - auto cutAt = 0; - auto limit = kPinnedMessageTextLimit; - auto size = original.text.size(); - for (; limit != 0;) { - --limit; - if (cutAt >= size) break; - if (original.text.at(cutAt).isLowSurrogate() - && (cutAt + 1 < size) - && original.text.at(cutAt + 1).isHighSurrogate()) { - cutAt += 2; - } else { - ++cutAt; - } - } - if (!limit && cutAt + 5 < size) { - original = Ui::Text::Mid(original, 0, cutAt).append( - Ui::kQEllipsis); - } - original = Ui::Text::Link( - Ui::Text::Filtered( - std::move(original), - { - EntityType::Spoiler, - EntityType::StrikeOut, - EntityType::Italic, - EntityType::CustomEmoji, - }), - 2); - result.text = tr::lng_action_pinned_message( - tr::now, - lt_from, - fromLinkText(), // Link 1. - lt_text, - std::move(original), // Link 2. - Ui::Text::WithEntities); - } else { - result.text = tr::lng_action_pinned_media( - tr::now, - lt_from, - fromLinkText(), // Link 1. - lt_media, - Ui::Text::Link(mediaText, 2), // Link 2. - Ui::Text::WithEntities); - } - } else if (pinned && pinned->msgId) { - result.links.push_back(fromLink()); - result.links.push_back(pinned->lnk); - result.text = tr::lng_action_pinned_media( - tr::now, - lt_from, - fromLinkText(), // Link 1. - lt_media, - Ui::Text::Link(tr::lng_contacts_loading(tr::now), 2), // Link 2. - Ui::Text::WithEntities); - } else { - result.links.push_back(fromLink()); - result.text = tr::lng_action_pinned_media( - tr::now, - lt_from, - fromLinkText(), // Link 1. - lt_media, - { .text = tr::lng_deleted_message(tr::now) }, - Ui::Text::WithEntities); - } - return result; -} - -HistoryService::PreparedText HistoryService::prepareGameScoreText() { - auto result = PreparedText {}; - auto gamescore = Get(); - - auto computeGameTitle = [&]() -> TextWithEntities { - if (gamescore && gamescore->msg) { - if (const auto media = gamescore->msg->media()) { - if (const auto game = media->game()) { - const auto row = 0; - const auto column = 0; - result.links.push_back( - std::make_shared( - &history()->owner(), - row, - column, - gamescore->msg->fullId())); - auto titleText = game->title; - return Ui::Text::Link(titleText, QString()); - } - } - return tr::lng_deleted_message(tr::now, Ui::Text::WithEntities); - } else if (gamescore && gamescore->msgId) { - return tr::lng_contacts_loading(tr::now, Ui::Text::WithEntities); - } - return {}; - }; - - const auto scoreNumber = gamescore ? gamescore->score : 0; - if (_from->isSelf()) { - auto gameTitle = computeGameTitle(); - if (gameTitle.text.isEmpty()) { - result.text = tr::lng_action_game_you_scored_no_game( - tr::now, - lt_count, - scoreNumber, - Ui::Text::WithEntities); - } else { - result.text = tr::lng_action_game_you_scored( - tr::now, - lt_count, - scoreNumber, - lt_game, - gameTitle, - Ui::Text::WithEntities); - } - } else { - result.links.push_back(fromLink()); - auto gameTitle = computeGameTitle(); - if (gameTitle.text.isEmpty()) { - result.text = tr::lng_action_game_score_no_game( - tr::now, - lt_count, - scoreNumber, - lt_from, - fromLinkText(), // Link 1. - Ui::Text::WithEntities); - } else { - result.text = tr::lng_action_game_score( - tr::now, - lt_count, - scoreNumber, - lt_from, - fromLinkText(), // Link 1. - lt_game, - gameTitle, - Ui::Text::WithEntities); - } - } - return result; -} - -HistoryService::PreparedText HistoryService::preparePaymentSentText() { - auto result = PreparedText {}; - const auto payment = Get(); - Assert(payment != nullptr); - - auto invoiceTitle = [&] { - if (payment->msg) { - if (const auto media = payment->msg->media()) { - if (const auto invoice = media->invoice()) { - return Ui::Text::Link(invoice->title, QString()); - } - } - } - return TextWithEntities(); - }(); - - if (invoiceTitle.text.isEmpty()) { - if (payment->recurringUsed) { - result.text = tr::lng_action_payment_used_recurring( - tr::now, - lt_amount, - { .text = payment->amount }, - Ui::Text::WithEntities); - } else { - result.text = (payment->recurringInit - ? tr::lng_action_payment_init_recurring - : tr::lng_action_payment_done)( - tr::now, - lt_amount, - { .text = payment->amount }, - lt_user, - { .text = history()->peer->name() }, - Ui::Text::WithEntities); - } - } else { - result.text = (payment->recurringInit - ? tr::lng_action_payment_init_recurring_for - : tr::lng_action_payment_done_for)( - tr::now, - lt_amount, - { .text = payment->amount }, - lt_user, - { .text = history()->peer->name() }, - lt_invoice, - invoiceTitle, - Ui::Text::WithEntities); - if (payment->msg) { - result.links.push_back(payment->lnk); - } - } - return result; -} - -HistoryService::PreparedText HistoryService::prepareCallScheduledText( - TimeId scheduleDate) { - const auto call = Get(); - Assert(call != nullptr); - - const auto scheduled = base::unixtime::parse(scheduleDate); - const auto date = scheduled.date(); - const auto now = QDateTime::currentDateTime(); - const auto secsToDateAddDays = [&](int days) { - return now.secsTo(QDateTime(date.addDays(days), QTime(0, 0))); - }; - auto result = PreparedText(); - const auto prepareWithDate = [&](const QString &date) { - if (history()->peer->isBroadcast()) { - result.text = tr::lng_action_group_call_scheduled_channel( - tr::now, - lt_date, - { .text = date }, - Ui::Text::WithEntities); - } else { - result.links.push_back(fromLink()); - result.text = tr::lng_action_group_call_scheduled_group( - tr::now, - lt_from, - fromLinkText(), // Link 1. - lt_date, - { .text = date }, - Ui::Text::WithEntities); - } - }; - const auto time = QLocale().toString(scheduled.time(), cTimeFormat()); - const auto prepareGeneric = [&] { - prepareWithDate(tr::lng_group_call_starts_date( - tr::now, - lt_date, - langDayOfMonthFull(date), - lt_time, - time)); - }; - auto nextIn = TimeId(0); - if (now.date().addDays(1) < scheduled.date()) { - nextIn = secsToDateAddDays(-1); - prepareGeneric(); - } else if (now.date().addDays(1) == scheduled.date()) { - nextIn = secsToDateAddDays(0); - prepareWithDate( - tr::lng_group_call_starts_tomorrow(tr::now, lt_time, time)); - } else if (now.date() == scheduled.date()) { - nextIn = secsToDateAddDays(1); - prepareWithDate( - tr::lng_group_call_starts_today(tr::now, lt_time, time)); - } else { - prepareGeneric(); - } - if (nextIn) { - call->lifetime = base::timer_once( - (nextIn + 2) * crl::time(1000) - ) | rpl::start_with_next([=] { - updateText(prepareCallScheduledText(scheduleDate)); - }); - } - return result; -} - -HistoryService::HistoryService( - not_null history, - MsgId id, - const MTPDmessage &data, - MessageFlags localFlags) -: HistoryItem( - history, - id, - FlagsFromMTP(id, data.vflags().v, localFlags), - data.vdate().v, - data.vfrom_id() ? peerFromMTP(*data.vfrom_id()) : PeerId(0)) { - createFromMtp(data); - applyTTL(data); -} - -HistoryService::HistoryService( - not_null history, - MsgId id, - const MTPDmessageService &data, - MessageFlags localFlags) -: HistoryItem( - history, - id, - FlagsFromMTP(id, data.vflags().v, localFlags), - data.vdate().v, - data.vfrom_id() ? peerFromMTP(*data.vfrom_id()) : PeerId(0)) { - createFromMtp(data); - applyTTL(data); -} - -HistoryService::HistoryService( - not_null history, - MsgId id, - MessageFlags flags, - TimeId date, - PreparedText &&message, - PeerId from, - PhotoData *photo) -: HistoryItem(history, id, flags, date, from) { - setServiceText(std::move(message)); - if (photo) { - _media = std::make_unique( - this, - history->peer, - photo); - } -} - -bool HistoryService::updateDependencyItem() { - if (GetDependentData()) { - return updateDependent(true); - } - return HistoryItem::updateDependencyItem(); -} - -bool HistoryService::needCheck() const { - return out() && !isEmpty(); -} - -ItemPreview HistoryService::toPreview(ToPreviewOptions options) const { - // Don't show for service messages (chat photo changed). - // Because larger version is shown exactly to the left of the preview. - //auto media = _media ? _media->toPreview(options) : ItemPreview(); - return { - .text = Ui::Text::Wrapped(notificationText(), EntityType::PlainLink), - //.images = std::move(media.images), - //.loadingContext = std::move(media.loadingContext), - }; -} - -TextWithEntities HistoryService::inReplyText() const { - auto result = HistoryService::notificationText(); - const auto &name = author()->name(); - TextUtilities::Trim(result); - if (result.text.startsWith(name)) { - result = Ui::Text::Mid(result, name.size()); - TextUtilities::Trim(result); - } - return Ui::Text::Wrapped(result, EntityType::PlainLink); -} - -MsgId HistoryService::replyToId() const { - return 0; // Don't render replies info in service, only handle threads. -} - -MsgId HistoryService::replyToTop() const { - if (const auto data = GetDependentData()) { - return data->topId; - } - return 0; -} - -MsgId HistoryService::topicRootId() const { - if (const auto data = GetDependentData() - ; data && data->topicPost && data->topId) { - return data->topId; - } else if (const auto info = Get()) { - if (info->created()) { - return id; - } - } - return Data::ForumTopic::kGeneralId; -} - -void HistoryService::setReplyFields( - MsgId replyTo, - MsgId replyToTop, - bool isForumPost) { - const auto data = GetDependentData(); - if (!data - || (data->topId == replyToTop) - || IsServerMsgId(data->topId) - || isScheduled()) { - return; - } - data->topId = replyToTop; - if (isForumPost) { - data->topicPost = true; - } - if (const auto topic = this->topic()) { - topic->maybeSetLastMessage(this); - } -} - -std::unique_ptr HistoryService::createView( - not_null delegate, - HistoryView::Element *replacing) { - return delegate->elementCreate(this, replacing); -} - -TextWithEntities HistoryService::fromLinkText() const { - return Ui::Text::Link(_from->name(), 1); -} - -ClickHandlerPtr HistoryService::fromLink() const { - return _from->createOpenLink(); -} - -void HistoryService::setServiceText(PreparedText &&prepared) { - const auto had = !_text.empty(); - _text = std::move(prepared.text); - _textLinks = std::move(prepared.links); - if (had) { - history()->owner().requestItemTextRefresh(this); - } -} - -void HistoryService::markMediaAsReadHook() { - if (const auto selfdestruct = Get()) { - if (!selfdestruct->destructAt) { - selfdestruct->destructAt = crl::now() + selfdestruct->timeToLive; - history()->owner().selfDestructIn(this, selfdestruct->timeToLive); - } - } -} - -crl::time HistoryService::getSelfDestructIn(crl::time now) { - if (auto selfdestruct = Get()) { - if (selfdestruct->destructAt > 0) { - if (selfdestruct->destructAt <= now) { - auto text = [selfdestruct] { - switch (selfdestruct->type) { - case HistoryServiceSelfDestruct::Type::Photo: return tr::lng_ttl_photo_expired(tr::now); - case HistoryServiceSelfDestruct::Type::Video: return tr::lng_ttl_video_expired(tr::now); - } - Unexpected("Type in HistoryServiceSelfDestruct::Type"); - }; - setServiceText({ TextWithEntities{ .text = text() } }); - return 0; - } - return selfdestruct->destructAt - now; - } - } - return 0; -} - -void HistoryService::createFromMtp(const MTPDmessage &message) { - const auto media = message.vmedia(); - Assert(media != nullptr); - - const auto mediaType = media->type(); - switch (mediaType) { - case mtpc_messageMediaPhoto: { - if (message.is_media_unread()) { - const auto &photo = media->c_messageMediaPhoto(); - const auto ttl = photo.vttl_seconds(); - Assert(ttl != nullptr); - - setSelfDestruct(HistoryServiceSelfDestruct::Type::Photo, ttl->v); - if (out()) { - setServiceText({ - tr::lng_ttl_photo_sent(tr::now, Ui::Text::WithEntities) - }); - } else { - auto result = PreparedText(); - result.links.push_back(fromLink()); - result.text = tr::lng_ttl_photo_received( - tr::now, - lt_from, - fromLinkText(), // Link 1. - Ui::Text::WithEntities); - setServiceText(std::move(result)); - } - } else { - setServiceText({ - tr::lng_ttl_photo_expired(tr::now, Ui::Text::WithEntities) - }); - } - } break; - case mtpc_messageMediaDocument: { - if (message.is_media_unread()) { - const auto &document = media->c_messageMediaDocument(); - const auto ttl = document.vttl_seconds(); - Assert(ttl != nullptr); - - setSelfDestruct(HistoryServiceSelfDestruct::Type::Video, ttl->v); - if (out()) { - setServiceText({ - tr::lng_ttl_video_sent(tr::now, Ui::Text::WithEntities) - }); - } else { - auto result = PreparedText(); - result.links.push_back(fromLink()); - result.text = tr::lng_ttl_video_received( - tr::now, - lt_from, - fromLinkText(), // Link 1. - Ui::Text::WithEntities); - setServiceText(std::move(result)); - } - } else { - setServiceText({ - tr::lng_ttl_video_expired(tr::now, Ui::Text::WithEntities) - }); - } - } break; - - default: Unexpected("Media type in HistoryService::createFromMtp()"); - } - - if (const auto reactions = message.vreactions()) { - updateReactions(reactions); - } -} - -void HistoryService::createFromMtp(const MTPDmessageService &message) { - const auto &action = message.vaction(); - const auto type = action.type(); - if (type == mtpc_messageActionPinMessage) { - UpdateComponents(HistoryServicePinned::Bit()); - } else if (type == mtpc_messageActionTopicCreate - || type == mtpc_messageActionTopicEdit) { - UpdateComponents(HistoryServiceTopicInfo::Bit()); - const auto info = Get(); - info->topicPost = true; - if (type == mtpc_messageActionTopicEdit) { - const auto &data = action.c_messageActionTopicEdit(); - if (const auto title = data.vtitle()) { - info->title = qs(*title); - info->renamed = true; - } - if (const auto icon = data.vicon_emoji_id()) { - info->iconId = icon->v; - info->reiconed = true; - } - if (const auto closed = data.vclosed()) { - info->closed = mtpIsTrue(*closed); - info->reopened = !info->closed; - } - if (const auto hidden = data.vhidden()) { - info->hidden = mtpIsTrue(*hidden); - info->unhidden = !info->hidden; - } - } else { - const auto &data = action.c_messageActionTopicCreate(); - info->title = qs(data.vtitle()); - info->iconId = data.vicon_emoji_id().value_or_empty(); - } - } else if (type == mtpc_messageActionSetChatTheme) { - setupChatThemeChange(); - } else if (type == mtpc_messageActionSetMessagesTTL) { - setupTTLChange(); - } else if (type == mtpc_messageActionGameScore) { - const auto &data = action.c_messageActionGameScore(); - UpdateComponents(HistoryServiceGameScore::Bit()); - Get()->score = data.vscore().v; - } else if (type == mtpc_messageActionPaymentSent) { - const auto &data = action.c_messageActionPaymentSent(); - UpdateComponents(HistoryServicePayment::Bit()); - const auto amount = data.vtotal_amount().v; - const auto currency = qs(data.vcurrency()); - const auto payment = Get(); - const auto id = fullId(); - const auto owner = &history()->owner(); - payment->slug = data.vinvoice_slug().value_or_empty(); - payment->recurringInit = data.is_recurring_init(); - payment->recurringUsed = data.is_recurring_used(); - payment->amount = Ui::FillAmountAndCurrency(amount, currency); - payment->invoiceLink = std::make_shared([=]( - ClickContext context) { - using namespace Payments; - const auto my = context.other.value(); - const auto weak = my.sessionWindow; - if (const auto item = owner->message(id)) { - CheckoutProcess::Start( - item, - Mode::Receipt, - crl::guard(weak, [=](auto) { weak->window().activate(); })); - } - }); - } else if (type == mtpc_messageActionGroupCall - || type == mtpc_messageActionGroupCallScheduled) { - const auto started = (type == mtpc_messageActionGroupCall); - const auto &callData = started - ? action.c_messageActionGroupCall().vcall() - : action.c_messageActionGroupCallScheduled().vcall(); - const auto duration = started - ? action.c_messageActionGroupCall().vduration() - : tl::conditional(); - if (duration) { - RemoveComponents(HistoryServiceOngoingCall::Bit()); - } else { - UpdateComponents(HistoryServiceOngoingCall::Bit()); - const auto call = Get(); - call->id = CallIdFromInput(callData); - call->link = GroupCallClickHandler(history()->peer, call->id); - } - } else if (type == mtpc_messageActionInviteToGroupCall) { - const auto &data = action.c_messageActionInviteToGroupCall(); - const auto id = CallIdFromInput(data.vcall()); - const auto peer = history()->peer; - const auto has = PeerHasThisCall(peer, id); - auto hasLink = !has.has_value() - ? PeerHasThisCallValue(peer, id) - : (*has) - ? PeerHasThisCallValue( - peer, - id) | rpl::skip(1) | rpl::type_erased() - : rpl::producer(); - if (!hasLink) { - RemoveComponents(HistoryServiceOngoingCall::Bit()); - } else { - UpdateComponents(HistoryServiceOngoingCall::Bit()); - const auto call = Get(); - call->id = id; - call->lifetime.destroy(); - - const auto users = data.vusers().v; - std::move(hasLink) | rpl::start_with_next([=](bool has) { - updateText(prepareInvitedToCallText(users, has ? id : 0)); - if (!has) { - RemoveComponents(HistoryServiceOngoingCall::Bit()); - } - }, call->lifetime); - } - } - if (const auto replyTo = message.vreply_to()) { - replyTo->match([&](const MTPDmessageReplyHeader &data) { - const auto peerId = data.vreply_to_peer_id() - ? peerFromMTP(*data.vreply_to_peer_id()) - : history()->peer->id; - if (const auto dependent = GetDependentData()) { - dependent->peerId = (peerId != history()->peer->id) - ? peerId - : 0; - const auto id = data.vreply_to_msg_id().v; - dependent->msgId = id; - dependent->topId = data.vreply_to_top_id().value_or(id); - dependent->topicPost = data.is_forum_topic() - || Has(); - if (!updateDependent()) { - RequestDependentMessageData( - this, - dependent->peerId, - dependent->msgId); - } - } - }); - } - setMessageByAction(action); -} - -const std::vector &HistoryService::customTextLinks() const { - return _textLinks; -} - -void HistoryService::applyEdition(const MTPDmessageService &message) { - clearDependency(); - UpdateComponents(0); - - createFromMtp(message); - applyServiceDateEdition(message); - - if (message.vaction().type() == mtpc_messageActionHistoryClear) { - removeMedia(); - finishEditionToEmpty(); - } else { - finishEdition(-1); - } -} - -void HistoryService::removeMedia() { - if (!_media) return; - - _media.reset(); - history()->owner().requestItemResize(this); -} - -Storage::SharedMediaTypesMask HistoryService::sharedMediaTypes() const { - if (auto media = this->media()) { - return media->sharedMediaTypes(); - } - return {}; -} - -void HistoryService::updateDependentText() { - auto text = PreparedText{}; - if (Has()) { - text = preparePinnedText(); - } else if (Has()) { - text = prepareGameScoreText(); - } else if (Has()) { - text = preparePaymentSentText(); - } else { - return; - } - updateText(std::move(text)); -} - -void HistoryService::updateText(PreparedText &&text) { - setServiceText(std::move(text)); - history()->owner().requestItemResize(this); - invalidateChatListEntry(); - history()->owner().updateDependentMessages(this); -} - -void HistoryService::clearDependency() { - if (const auto dependent = GetDependentData()) { - if (dependent->msg) { - history()->owner().unregisterDependentMessage( - this, - dependent->msg); - dependent->msg = nullptr; - dependent->msgId = 0; - } - } -} - -void HistoryService::setupChatThemeChange() { - if (const auto user = history()->peer->asUser()) { - auto link = std::make_shared([=]( - ClickContext context) { - const auto my = context.other.value(); - if (const auto controller = my.sessionWindow.get()) { - controller->toggleChooseChatTheme(user); - } - }); - - UpdateComponents(HistoryServiceChatThemeChange::Bit()); - Get()->link = std::move(link); - } else { - RemoveComponents(HistoryServiceChatThemeChange::Bit()); - } -} - -void HistoryService::setupTTLChange() { - const auto peer = history()->peer; - auto link = std::make_shared([=]( - ClickContext context) { - const auto my = context.other.value(); - if (const auto controller = my.sessionWindow.get()) { - const auto validator = TTLMenu::TTLValidator( - std::make_shared(controller), - peer); - if (validator.can()) { - validator.showBox(); - } - } - }); - - UpdateComponents(HistoryServiceTTLChange::Bit()); - Get()->link = std::move(link); -} - -void HistoryService::dependencyItemRemoved(HistoryItem *dependency) { - clearDependency(); - updateDependentText(); -} - -HistoryService::~HistoryService() { - clearDependency(); - _media.reset(); -} - -HistoryService::PreparedText GenerateJoinedText( - not_null history, - not_null inviter, - bool viaRequest) { - if (inviter->id != history->session().userPeerId()) { - auto result = HistoryService::PreparedText{}; - result.links.push_back(inviter->createOpenLink()); - result.text = (history->peer->isMegagroup() - ? tr::lng_action_add_you_group - : tr::lng_action_add_you)( - tr::now, - lt_from, - Ui::Text::Link(inviter->name(), QString()), - Ui::Text::WithEntities); - return result; - } else if (history->peer->isMegagroup()) { - if (viaRequest) { - return { tr::lng_action_you_joined_by_request( - tr::now, - Ui::Text::WithEntities) }; - } - auto self = history->session().user(); - auto result = HistoryService::PreparedText{}; - result.links.push_back(self->createOpenLink()); - result.text = tr::lng_action_user_joined( - tr::now, - lt_from, - Ui::Text::Link(self->name(), QString()), - Ui::Text::WithEntities); - return result; - } - return { viaRequest - ? tr::lng_action_you_joined_by_request_channel( - tr::now, - Ui::Text::WithEntities) - : tr::lng_action_you_joined(tr::now, Ui::Text::WithEntities) }; -} - -not_null GenerateJoinedMessage( - not_null history, - TimeId inviteDate, - not_null inviter, - bool viaRequest) { - return history->makeServiceMessage( - history->owner().nextLocalMessageId(), - MessageFlag::Local, - inviteDate, - GenerateJoinedText(history, inviter, viaRequest)); -} - -std::optional PeerHasThisCall( - not_null peer, - CallId id) { - const auto call = peer->groupCall(); - return call - ? std::make_optional(call->id() == id) - : PeerCallKnown(peer) - ? std::make_optional(false) - : std::nullopt; -} diff --git a/Telegram/SourceFiles/history/history_service.h b/Telegram/SourceFiles/history/history_service.h deleted file mode 100644 index 506f3677d..000000000 --- a/Telegram/SourceFiles/history/history_service.h +++ /dev/null @@ -1,235 +0,0 @@ -/* -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 -*/ -#pragma once - -#include "history/history_item.h" - -namespace HistoryView { -class Service; -} // namespace HistoryView - -struct HistoryServiceDependentData { - PeerId peerId = 0; - HistoryItem *msg = nullptr; - ClickHandlerPtr lnk; - MsgId msgId = 0; - MsgId topId = 0; - bool topicPost = false; -}; - -struct HistoryServicePinned -: public RuntimeComponent -, public HistoryServiceDependentData { -}; - -struct HistoryServiceTopicInfo -: public RuntimeComponent -, public HistoryServiceDependentData { - QString title; - DocumentId iconId = 0; - bool closed = false; - bool reopened = false; - bool reiconed = false; - bool renamed = false; - bool hidden = false; - bool unhidden = false; - - [[nodiscard]] bool created() const { - return !closed - && !reopened - && !reiconed - && !renamed - && !hidden - && !unhidden; - } -}; - -struct HistoryServiceGameScore -: public RuntimeComponent -, public HistoryServiceDependentData { - int score = 0; -}; - -struct HistoryServicePayment -: public RuntimeComponent -, public HistoryServiceDependentData { - QString slug; - QString amount; - ClickHandlerPtr invoiceLink; - bool recurringInit = false; - bool recurringUsed = false; -}; - -struct HistoryServiceSelfDestruct -: public RuntimeComponent { - enum class Type { - Photo, - Video, - }; - Type type = Type::Photo; - crl::time timeToLive = 0; - crl::time destructAt = 0; -}; - -struct HistoryServiceOngoingCall -: public RuntimeComponent { - CallId id = 0; - ClickHandlerPtr link; - rpl::lifetime lifetime; -}; - -struct HistoryServiceChatThemeChange -: public RuntimeComponent { - ClickHandlerPtr link; -}; - -struct HistoryServiceTTLChange -: public RuntimeComponent { - ClickHandlerPtr link; -}; - -namespace HistoryView { -class ServiceMessagePainter; -} // namespace HistoryView - -class HistoryService : public HistoryItem { -public: - struct PreparedText { - TextWithEntities text; - std::vector links; - }; - - HistoryService( - not_null history, - MsgId id, - const MTPDmessage &data, - MessageFlags localFlags); - HistoryService( - not_null history, - MsgId id, - const MTPDmessageService &data, - MessageFlags localFlags); - HistoryService( - not_null history, - MsgId id, - MessageFlags flags, - TimeId date, - PreparedText &&message, - PeerId from = 0, - PhotoData *photo = nullptr); - - bool updateDependencyItem() override; - MsgId dependencyMsgId() const override { - if (auto dependent = GetDependentData()) { - return dependent->msgId; - } - return 0; - } - bool notificationReady() const override { - if (auto dependent = GetDependentData()) { - return (dependent->msg || !dependent->msgId); - } - return true; - } - - const std::vector &customTextLinks() const override; - - void applyEdition(const MTPDmessageService &message) override; - crl::time getSelfDestructIn(crl::time now) override; - - Storage::SharedMediaTypesMask sharedMediaTypes() const override; - - void dependencyItemRemoved(HistoryItem *dependency) override; - - bool needCheck() const override; - bool isService() const override { - return true; - } - ItemPreview toPreview(ToPreviewOptions options) const override; - TextWithEntities inReplyText() const override; - - MsgId replyToId() const override; - MsgId replyToTop() const override; - MsgId topicRootId() const override; - void setReplyFields( - MsgId replyTo, - MsgId replyToTop, - bool isForumPost) override; - - std::unique_ptr createView( - not_null delegate, - HistoryView::Element *replacing = nullptr) override; - - void setServiceText(PreparedText &&prepared); - - ~HistoryService(); - -protected: - friend class HistoryView::ServiceMessagePainter; - - void markMediaAsReadHook() override; - - TextWithEntities fromLinkText() const; - ClickHandlerPtr fromLink() const; - - void removeMedia(); - -private: - HistoryServiceDependentData *GetDependentData() { - if (const auto pinned = Get()) { - return pinned; - } else if (const auto gamescore = Get()) { - return gamescore; - } else if (const auto payment = Get()) { - return payment; - } else if (const auto info = Get()) { - return info; - } - return nullptr; - } - const HistoryServiceDependentData *GetDependentData() const { - return const_cast(this)->GetDependentData(); - } - bool updateDependent(bool force = false); - void updateDependentText(); - void updateText(PreparedText &&text); - void clearDependency(); - void setupChatThemeChange(); - void setupTTLChange(); - - void createFromMtp(const MTPDmessage &message); - void createFromMtp(const MTPDmessageService &message); - void setMessageByAction(const MTPmessageAction &action); - void setSelfDestruct( - HistoryServiceSelfDestruct::Type type, - int ttlSeconds); - void applyAction(const MTPMessageAction &action); - - PreparedText preparePinnedText(); - PreparedText prepareGameScoreText(); - PreparedText preparePaymentSentText(); - PreparedText prepareInvitedToCallText( - const QVector &users, - CallId linkCallId); - PreparedText prepareCallScheduledText( - TimeId scheduleDate); - - friend class HistoryView::Service; - - std::vector _textLinks; - -}; - -[[nodiscard]] not_null GenerateJoinedMessage( - not_null history, - TimeId inviteDate, - not_null inviter, - bool viaRequest); -[[nodiscard]] std::optional PeerHasThisCall( - not_null peer, - CallId id); diff --git a/Telegram/SourceFiles/history/history_widget.cpp b/Telegram/SourceFiles/history/history_widget.cpp index d9a0ec902..a6f6c57c4 100644 --- a/Telegram/SourceFiles/history/history_widget.cpp +++ b/Telegram/SourceFiles/history/history_widget.cpp @@ -78,7 +78,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/stickers/data_custom_emoji.h" #include "history/history.h" #include "history/history_item.h" -#include "history/history_message.h" +#include "history/history_item_helpers.h" // GetErrorTextForSending. #include "history/history_drag_area.h" #include "history/history_inner_widget.h" #include "history/history_item_components.h" diff --git a/Telegram/SourceFiles/history/view/controls/history_view_forward_panel.cpp b/Telegram/SourceFiles/history/view/controls/history_view_forward_panel.cpp index 33dfd7799..3265403d4 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_forward_panel.cpp +++ b/Telegram/SourceFiles/history/view/controls/history_view_forward_panel.cpp @@ -8,7 +8,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/view/controls/history_view_forward_panel.h" #include "history/history.h" -#include "history/history_message.h" +#include "history/history_item.h" +#include "history/history_item_helpers.h" #include "history/history_item_components.h" #include "history/view/history_view_item_preview.h" #include "data/data_session.h" diff --git a/Telegram/SourceFiles/history/view/history_view_bottom_info.cpp b/Telegram/SourceFiles/history/view/history_view_bottom_info.cpp index ce88d565b..8f9ed8dda 100644 --- a/Telegram/SourceFiles/history/view/history_view_bottom_info.cpp +++ b/Telegram/SourceFiles/history/view/history_view_bottom_info.cpp @@ -15,7 +15,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/painter.h" #include "lang/lang_keys.h" #include "history/history_item_components.h" -#include "history/history_message.h" +#include "history/history_item.h" #include "history/history.h" #include "history/view/history_view_message.h" #include "history/view/history_view_cursor_state.h" @@ -629,7 +629,7 @@ void BottomInfo::continueReactionAnimations(base::flat_map< BottomInfo::Data BottomInfoDataFromMessage(not_null message) { using Flag = BottomInfo::Data::Flag; - const auto item = message->message(); + const auto item = message->data(); auto result = BottomInfo::Data(); result.date = message->dateTime(); diff --git a/Telegram/SourceFiles/history/view/history_view_context_menu.cpp b/Telegram/SourceFiles/history/view/history_view_context_menu.cpp index 38ec61142..becd4f422 100644 --- a/Telegram/SourceFiles/history/view/history_view_context_menu.cpp +++ b/Telegram/SourceFiles/history/view/history_view_context_menu.cpp @@ -19,7 +19,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/view/history_view_cursor_state.h" #include "history/history.h" #include "history/history_item.h" -#include "history/history_message.h" #include "history/history_item_text.h" #include "history/view/history_view_schedule_box.h" #include "history/view/media/history_view_media.h" diff --git a/Telegram/SourceFiles/history/view/history_view_element.cpp b/Telegram/SourceFiles/history/view/history_view_element.cpp index b1b0783d2..73a6d572a 100644 --- a/Telegram/SourceFiles/history/view/history_view_element.cpp +++ b/Telegram/SourceFiles/history/view/history_view_element.cpp @@ -10,8 +10,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "api/api_chat_invite.h" #include "history/view/history_view_service_message.h" #include "history/view/history_view_message.h" -#include "history/history_item_components.h" -#include "history/history_item.h" #include "history/view/media/history_view_media.h" #include "history/view/media/history_view_media_grouped.h" #include "history/view/media/history_view_sticker.h" @@ -22,7 +20,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/view/history_view_cursor_state.h" #include "history/view/history_view_spoiler_click_handler.h" #include "history/history.h" -#include "history/history_service.h" +#include "history/history_item.h" +#include "history/history_item_components.h" +#include "history/history_item_helpers.h" #include "base/unixtime.h" #include "core/application.h" #include "core/core_settings.h" @@ -118,18 +118,6 @@ SimpleElementDelegate::SimpleElementDelegate( SimpleElementDelegate::~SimpleElementDelegate() = default; -std::unique_ptr SimpleElementDelegate::elementCreate( - not_null message, - Element *replacing) { - return std::make_unique(this, message, replacing); -} - -std::unique_ptr SimpleElementDelegate::elementCreate( - not_null message, - Element *replacing) { - return std::make_unique(this, message, replacing); -} - bool SimpleElementDelegate::elementUnderCursor( not_null view) { return false; diff --git a/Telegram/SourceFiles/history/view/history_view_element.h b/Telegram/SourceFiles/history/view/history_view_element.h index 8e9e19a26..ec6807995 100644 --- a/Telegram/SourceFiles/history/view/history_view_element.h +++ b/Telegram/SourceFiles/history/view/history_view_element.h @@ -15,8 +15,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL class History; class HistoryBlock; class HistoryItem; -class HistoryMessage; -class HistoryService; struct HistoryMessageReply; namespace Data { @@ -64,12 +62,6 @@ class Element; class ElementDelegate { public: virtual Context elementContext() = 0; - virtual std::unique_ptr elementCreate( - not_null message, - Element *replacing = nullptr) = 0; - virtual std::unique_ptr elementCreate( - not_null message, - Element *replacing = nullptr) = 0; virtual bool elementUnderCursor(not_null view) = 0; [[nodiscard]] virtual float64 elementHighlightOpacity( not_null item) const = 0; @@ -126,12 +118,6 @@ public: Fn update); ~SimpleElementDelegate(); - std::unique_ptr elementCreate( - not_null message, - Element *replacing = nullptr) override; - std::unique_ptr elementCreate( - not_null message, - Element *replacing = nullptr) override; bool elementUnderCursor(not_null view) override; [[nodiscard]] float64 elementHighlightOpacity( not_null item) const override; diff --git a/Telegram/SourceFiles/history/view/history_view_list_widget.cpp b/Telegram/SourceFiles/history/view/history_view_list_widget.cpp index edb83f268..1a457d178 100644 --- a/Telegram/SourceFiles/history/view/history_view_list_widget.cpp +++ b/Telegram/SourceFiles/history/view/history_view_list_widget.cpp @@ -10,8 +10,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "base/unixtime.h" #include "base/qt/qt_key_modifiers.h" #include "base/qt/qt_common_adapters.h" -#include "history/history_message.h" +#include "history/history_item.h" #include "history/history_item_components.h" +#include "history/history_item_helpers.h" #include "history/history_item_text.h" #include "history/view/media/history_view_media.h" #include "history/view/media/history_view_sticker.h" @@ -1614,18 +1615,6 @@ Context ListWidget::elementContext() { return _delegate->listContext(); } -std::unique_ptr ListWidget::elementCreate( - not_null message, - Element *replacing) { - return std::make_unique(this, message, replacing); -} - -std::unique_ptr ListWidget::elementCreate( - not_null message, - Element *replacing) { - return std::make_unique(this, message, replacing); -} - bool ListWidget::elementUnderCursor( not_null view) { return (_overElement == view); diff --git a/Telegram/SourceFiles/history/view/history_view_list_widget.h b/Telegram/SourceFiles/history/view/history_view_list_widget.h index 0dbeb7eef..e33194d94 100644 --- a/Telegram/SourceFiles/history/view/history_view_list_widget.h +++ b/Telegram/SourceFiles/history/view/history_view_list_widget.h @@ -286,12 +286,6 @@ public: // ElementDelegate interface. Context elementContext() override; - std::unique_ptr elementCreate( - not_null message, - Element *replacing = nullptr) override; - std::unique_ptr elementCreate( - not_null message, - Element *replacing = nullptr) override; bool elementUnderCursor(not_null view) override; [[nodiscard]] float64 elementHighlightOpacity( not_null item) const override; diff --git a/Telegram/SourceFiles/history/view/history_view_message.cpp b/Telegram/SourceFiles/history/view/history_view_message.cpp index c467ed3c1..c27aeed28 100644 --- a/Telegram/SourceFiles/history/view/history_view_message.cpp +++ b/Telegram/SourceFiles/history/view/history_view_message.cpp @@ -10,8 +10,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "core/click_handler_types.h" // ClickHandlerContext #include "core/ui_integration.h" #include "history/view/history_view_cursor_state.h" +#include "history/history_item.h" #include "history/history_item_components.h" -#include "history/history_message.h" +#include "history/history_item_helpers.h" #include "history/view/media/history_view_media.h" #include "history/view/media/history_view_web_page.h" #include "history/view/reactions/history_view_reactions.h" @@ -313,7 +314,7 @@ LogEntryOriginal::~LogEntryOriginal() = default; Message::Message( not_null delegate, - not_null data, + not_null data, Element *replacing) : Element(delegate, data, replacing, Flag(0)) , _bottomInfo( @@ -348,10 +349,6 @@ Message::~Message() { } } -not_null Message::message() const { - return static_cast(data().get()); -} - void Message::refreshRightBadge() { const auto text = [&] { if (data()->isDiscussionPost()) { @@ -403,7 +400,7 @@ void Message::applyGroupAdminChanges( } void Message::animateReaction(Ui::ReactionFlyAnimationArgs &&args) { - const auto item = message(); + const auto item = data(); const auto media = this->media(); auto g = countGeometry(); @@ -510,7 +507,7 @@ auto Message::takeReactionAnimations() } QSize Message::performCountOptimalSize() { - const auto item = message(); + const auto item = data(); const auto markup = item->inlineReplyMarkup(); refreshIsTopicRootReply(); validateText(); @@ -689,7 +686,7 @@ QSize Message::performCountOptimalSize() { } void Message::refreshTopicButton() { - const auto item = message(); + const auto item = data(); if (isAttachedToPrevious() || context() != Context::History) { _topicButton = nullptr; } else if (const auto topic = item->topic()) { @@ -742,7 +739,7 @@ void Message::draw(Painter &p, const PaintContext &context) const { return; } - const auto item = message(); + const auto item = data(); const auto media = this->media(); const auto stm = context.messageStyle(); @@ -1039,7 +1036,7 @@ void Message::draw(Painter &p, const PaintContext &context) const { } if (const auto reply = displayedReply()) { - if (reply->isNameUpdated(message())) { + if (reply->isNameUpdated(data())) { const_cast(this)->setPendingResize(); } } @@ -1174,7 +1171,7 @@ void Message::paintFromName( Painter &p, QRect &trect, const PaintContext &context) const { - const auto item = message(); + const auto item = data(); if (!displayFromName()) { return; } @@ -1367,7 +1364,7 @@ void Message::paintForwardedInfo( QRect &trect, const PaintContext &context) const { if (displayForwardedFrom()) { - const auto item = message(); + const auto item = data(); const auto st = context.st; const auto stm = context.messageStyle(); const auto forwarded = item->Get(); @@ -1438,7 +1435,7 @@ void Message::paintViaBotIdInfo( Painter &p, QRect &trect, const PaintContext &context) const { - const auto item = message(); + const auto item = data(); if (!displayFromName() && !displayForwardedFrom()) { if (auto via = item->Get()) { const auto stm = context.messageStyle(); @@ -1479,7 +1476,7 @@ PointState Message::pointState(QPoint point) const { } const auto media = this->media(); - const auto item = message(); + const auto item = data(); const auto reactionsInBubble = _reactions && embedReactionsInBubble(); if (drawBubble()) { if (!g.contains(point)) { @@ -1744,7 +1741,7 @@ void Message::unloadHeavyPart() { bool Message::showForwardsFromSender( not_null forwarded) const { - const auto peer = message()->history()->peer; + const auto peer = data()->history()->peer; return peer->isSelf() || peer->isRepliesChat() || forwarded->imported; @@ -1760,7 +1757,7 @@ bool Message::hasFromPhoto() const { case Context::History: case Context::Pinned: case Context::Replies: { - const auto item = message(); + const auto item = data(); if (item->isPost()) { if (item->isSponsored()) { if (item->history()->peer->isMegagroup()) { @@ -1794,7 +1791,7 @@ bool Message::hasFromPhoto() const { TextState Message::textState( QPoint point, StateRequest request) const { - const auto item = message(); + const auto item = data(); const auto media = this->media(); auto result = TextState(item); @@ -2067,7 +2064,7 @@ bool Message::getStateFromName( if (replyWidth) { availableWidth -= st::msgPadding.right() + replyWidth; } - const auto item = message(); + const auto item = data(); const auto from = item->displayFrom(); const auto nameText = [&]() -> const Ui::Text::String * { if (from) { @@ -2138,7 +2135,7 @@ bool Message::getStateForwardedInfo( if (!displayForwardedFrom()) { return false; } - const auto item = message(); + const auto item = data(); const auto forwarded = item->Get(); const auto skip1 = forwarded->psaType.isEmpty() ? 0 @@ -2252,7 +2249,7 @@ bool Message::getStateViaBotIdInfo( QPoint point, QRect &trect, not_null outResult) const { - const auto item = message(); + const auto item = data(); if (const auto via = item->Get()) { if (!displayFromName() && !displayForwardedFrom()) { if (QRect(trect.x(), trect.y(), via->width, st::msgNameFont->height).contains(point)) { @@ -2273,7 +2270,7 @@ bool Message::getStateText( if (!hasVisibleText()) { return false; } - const auto item = message(); + const auto item = data(); if (base::in_range(point.y(), trect.y(), trect.y() + trect.height())) { *outResult = TextState(item, text().getState( point - trect.topLeft(), @@ -2286,7 +2283,7 @@ bool Message::getStateText( // Forward to media. void Message::updatePressed(QPoint point) { - const auto item = message(); + const auto item = data(); const auto media = this->media(); if (!media) return; @@ -2759,7 +2756,7 @@ void Message::updateViewButtonExistence() { } void Message::initLogEntryOriginal() { - if (const auto log = message()->Get()) { + if (const auto log = data()->Get()) { AddComponents(LogEntryOriginal::Bit()); const auto entry = Get(); entry->page = std::make_unique(this, log->page); @@ -2767,7 +2764,7 @@ void Message::initLogEntryOriginal() { } void Message::initPsa() { - if (const auto forwarded = message()->Get()) { + if (const auto forwarded = data()->Get()) { if (!forwarded->psaType.isEmpty()) { AddComponents(PsaTooltipState::Bit()); Get()->type = forwarded->psaType; @@ -2810,7 +2807,7 @@ bool Message::hasFromName() const { case Context::History: case Context::Pinned: case Context::Replies: { - const auto item = message(); + const auto item = data(); const auto peer = item->history()->peer; if (hasOutLayout() && !item->from()->isChannel()) { return false; @@ -2841,7 +2838,7 @@ bool Message::displayFromName() const { } bool Message::displayForwardedFrom() const { - const auto item = message(); + const auto item = data(); if (const auto forwarded = item->Get()) { if (showForwardsFromSender(forwarded)) { return false; @@ -2858,7 +2855,7 @@ bool Message::displayForwardedFrom() const { } bool Message::hasOutLayout() const { - const auto item = message(); + const auto item = data(); if (item->history()->peer->isSelf()) { return !item->Has(); } else if (const auto forwarded = item->Get()) { @@ -2874,7 +2871,7 @@ bool Message::hasOutLayout() const { } bool Message::drawBubble() const { - const auto item = message(); + const auto item = data(); if (isHidden()) { return false; } else if (logEntryOriginal()) { @@ -2895,7 +2892,7 @@ TopicButton *Message::displayedTopicButton() const { } bool Message::unwrapped() const { - const auto item = message(); + const auto item = data(); if (isHidden()) { return true; } else if (logEntryOriginal()) { @@ -2989,7 +2986,7 @@ std::optional Message::rightActionSize() const { } bool Message::displayFastShare() const { - const auto item = message(); + const auto item = data(); const auto peer = item->history()->peer; if (!item->allowsForward()) { return false; @@ -3015,7 +3012,7 @@ bool Message::displayGoToOriginal() const { if (isPinnedContext()) { return !hasOutLayout(); } - const auto item = message(); + const auto item = data(); if (const auto forwarded = item->Get()) { return forwarded->savedFromPeer && forwarded->savedFromMsgId @@ -3116,7 +3113,7 @@ void Message::ensureRightAction() const { ClickHandlerPtr Message::prepareRightActionLink() const { if (isPinnedContext()) { - return goToMessageClickHandler(data()); + return JumpToMessageClickHandler(data()); } else if (displayRightActionComments()) { return createGoToCommentsLink(); } @@ -3207,7 +3204,7 @@ bool Message::isPinnedContext() const { } void Message::updateMediaInBubbleState() { - const auto item = message(); + const auto item = data(); const auto media = this->media(); if (media) { @@ -3271,7 +3268,7 @@ void Message::updateMediaInBubbleState() { } void Message::fromNameUpdated(int width) const { - const auto item = message(); + const auto item = data(); const auto replyWidth = hasFastReply() ? st::msgFont->width(FastReplyText()) : 0; @@ -3356,7 +3353,7 @@ QRect Message::innerGeometry() const { } if (!displayFromName() && !displayForwardedFrom()) { // See paintViaBotIdInfo(). - if (message()->Has()) { + if (data()->Has()) { result.translate(0, st::msgServiceNameFont->height); } } @@ -3468,7 +3465,7 @@ int Message::resizeContentGetHeight(int newWidth) { auto newHeight = minHeight(); - const auto item = message(); + const auto item = data(); const auto media = this->media(); const auto mediaDisplayed = media ? media->isDisplayed() : false; const auto bubble = drawBubble(); @@ -3635,7 +3632,7 @@ bool Message::needInfoDisplay() const { } bool Message::hasVisibleText() const { - if (message()->emptyText()) { + if (data()->emptyText()) { return false; } const auto media = this->media(); @@ -3660,7 +3657,7 @@ QSize Message::performCountCurrentSize(int newWidth) { } void Message::refreshInfoSkipBlock() { - const auto item = message(); + const auto item = data(); const auto media = this->media(); const auto hasTextSkipBlock = [&] { if (item->_text.empty()) { @@ -3687,7 +3684,7 @@ void Message::refreshInfoSkipBlock() { } TimeId Message::displayedEditDate() const { - const auto item = message(); + const auto item = data(); const auto overrided = media() && media()->overrideEditedDate(); if (item->hideEditedBadge() && !overrided) { return TimeId(0); @@ -3703,7 +3700,7 @@ HistoryMessageEdited *Message::displayedEditBadge() { return media->displayedEditBadge(); } } - return message()->Get(); + return data()->Get(); } const HistoryMessageEdited *Message::displayedEditBadge() const { @@ -3712,7 +3709,7 @@ const HistoryMessageEdited *Message::displayedEditBadge() const { return media->displayedEditBadge(); } } - return message()->Get(); + return data()->Get(); } } // namespace HistoryView diff --git a/Telegram/SourceFiles/history/view/history_view_message.h b/Telegram/SourceFiles/history/view/history_view_message.h index 16b76f0d3..f9fd8afa7 100644 --- a/Telegram/SourceFiles/history/view/history_view_message.h +++ b/Telegram/SourceFiles/history/view/history_view_message.h @@ -11,7 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/view/history_view_bottom_info.h" #include "ui/effects/animations.h" -class HistoryMessage; +class HistoryItem; struct HistoryMessageEdited; struct HistoryMessageForwarded; struct HistoryMessageReplyMarkup; @@ -64,7 +64,7 @@ class Message final : public Element { public: Message( not_null delegate, - not_null data, + not_null data, Element *replacing); ~Message(); @@ -72,8 +72,6 @@ public: const ClickHandlerPtr &handler, bool pressed) override; - not_null message() const; - [[nodiscard]] const HistoryMessageEdited *displayedEditBadge() const; [[nodiscard]] HistoryMessageEdited *displayedEditBadge(); diff --git a/Telegram/SourceFiles/history/view/history_view_service_message.cpp b/Telegram/SourceFiles/history/view/history_view_service_message.cpp index d1d1556dc..167d5f0e2 100644 --- a/Telegram/SourceFiles/history/view/history_view_service_message.cpp +++ b/Telegram/SourceFiles/history/view/history_view_service_message.cpp @@ -7,11 +7,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "history/view/history_view_service_message.h" -#include "history/history.h" -#include "history/history_service.h" #include "history/view/media/history_view_media.h" -#include "history/history_item_components.h" #include "history/view/history_view_cursor_state.h" +#include "history/history.h" +#include "history/history_item.h" +#include "history/history_item_components.h" +#include "history/history_item_helpers.h" #include "data/data_abstract_structure.h" #include "data/data_chat.h" #include "data/data_channel.h" @@ -400,15 +401,11 @@ QVector ServiceMessagePainter::CountLineWidths( Service::Service( not_null delegate, - not_null data, + not_null data, Element *replacing) : Element(delegate, data, replacing, Flag::ServiceMessage) { } -not_null Service::message() const { - return static_cast(data().get()); -} - QRect Service::innerGeometry() const { return countGeometry(); } @@ -592,7 +589,7 @@ PointState Service::pointState(QPoint point) const { } TextState Service::textState(QPoint point, StateRequest request) const { - const auto item = message(); + const auto item = data(); const auto media = this->media(); auto result = TextState(item); diff --git a/Telegram/SourceFiles/history/view/history_view_service_message.h b/Telegram/SourceFiles/history/view/history_view_service_message.h index 73a51a480..5ee09aa7e 100644 --- a/Telegram/SourceFiles/history/view/history_view_service_message.h +++ b/Telegram/SourceFiles/history/view/history_view_service_message.h @@ -9,7 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/view/history_view_element.h" -class HistoryService; +class HistoryItem; namespace Ui { class ChatStyle; @@ -30,7 +30,7 @@ class Service final : public Element { public: Service( not_null delegate, - not_null data, + not_null data, Element *replacing); int marginTop() const override; @@ -50,9 +50,7 @@ public: QRect innerGeometry() const override; private: - not_null message() const; - - QRect countGeometry() const; + [[nodiscard]] QRect countGeometry() const; QSize performCountOptimalSize() override; QSize performCountCurrentSize(int newWidth) override; diff --git a/Telegram/SourceFiles/history/view/media/history_view_media_grouped.cpp b/Telegram/SourceFiles/history/view/media/history_view_media_grouped.cpp index 09d2df63e..a4310c817 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_media_grouped.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_media_grouped.cpp @@ -8,7 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/view/media/history_view_media_grouped.h" #include "history/history_item_components.h" -#include "history/history_message.h" +#include "history/history_item.h" #include "history/history.h" #include "history/view/history_view_element.h" #include "history/view/history_view_cursor_state.h" diff --git a/Telegram/SourceFiles/history/view/reactions/history_view_reactions.cpp b/Telegram/SourceFiles/history/view/reactions/history_view_reactions.cpp index 616f8741b..9c524d643 100644 --- a/Telegram/SourceFiles/history/view/reactions/history_view_reactions.cpp +++ b/Telegram/SourceFiles/history/view/reactions/history_view_reactions.cpp @@ -7,7 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "history/view/reactions/history_view_reactions.h" -#include "history/history_message.h" +#include "history/history_item.h" #include "history/history.h" #include "history/view/history_view_message.h" #include "history/view/history_view_cursor_state.h" @@ -604,7 +604,7 @@ void InlineList::continueAnimations(base::flat_map< InlineListData InlineListDataFromMessage(not_null message) { using Flag = InlineListData::Flag; - const auto item = message->message(); + const auto item = message->data(); auto result = InlineListData(); result.reactions = item->reactions(); if (const auto user = item->history()->peer->asUser()) { diff --git a/Telegram/SourceFiles/info/downloads/info_downloads_provider.cpp b/Telegram/SourceFiles/info/downloads/info_downloads_provider.cpp index 45c5f7599..36059ac33 100644 --- a/Telegram/SourceFiles/info/downloads/info_downloads_provider.cpp +++ b/Telegram/SourceFiles/info/downloads/info_downloads_provider.cpp @@ -18,6 +18,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "main/main_session.h" #include "main/main_account.h" #include "history/history_item.h" +#include "history/history_item_helpers.h" #include "history/history.h" #include "core/application.h" #include "storage/storage_shared_media.h" diff --git a/Telegram/SourceFiles/info/media/info_media_list_widget.cpp b/Telegram/SourceFiles/info/media/info_media_list_widget.cpp index 2f053fbdc..ffce0bb00 100644 --- a/Telegram/SourceFiles/info/media/info_media_list_widget.cpp +++ b/Telegram/SourceFiles/info/media/info_media_list_widget.cpp @@ -26,6 +26,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_download_manager.h" #include "data/data_forum_topic.h" #include "history/history_item.h" +#include "history/history_item_helpers.h" #include "history/history.h" #include "history/view/history_view_cursor_state.h" #include "history/view/history_view_service_message.h" @@ -892,7 +893,7 @@ void ListWidget::showContextMenu( tr::lng_context_to_msg(tr::now), [=] { if (const auto item = MessageByGlobalId(globalId)) { - goToMessageClickHandler(item)->onClick({}); + JumpToMessageClickHandler(item)->onClick({}); } }, &st::menuIconShowInChat); diff --git a/Telegram/SourceFiles/info/media/info_media_provider.cpp b/Telegram/SourceFiles/info/media/info_media_provider.cpp index 6bec9e04b..6d17213d1 100644 --- a/Telegram/SourceFiles/info/media/info_media_provider.cpp +++ b/Telegram/SourceFiles/info/media/info_media_provider.cpp @@ -15,6 +15,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "lang/lang_keys.h" #include "history/history.h" #include "history/history_item.h" +#include "history/history_item_helpers.h" #include "data/data_session.h" #include "data/data_chat.h" #include "data/data_channel.h" diff --git a/Telegram/SourceFiles/inline_bots/inline_bot_send_data.cpp b/Telegram/SourceFiles/inline_bots/inline_bot_send_data.cpp index 50b96f1be..76cb9cb27 100644 --- a/Telegram/SourceFiles/inline_bots/inline_bot_send_data.cpp +++ b/Telegram/SourceFiles/inline_bots/inline_bot_send_data.cpp @@ -13,7 +13,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "storage/localstorage.h" #include "lang/lang_keys.h" #include "history/history.h" -#include "history/history_message.h" +#include "history/history_item.h" #include "data/data_channel.h" #include "ui/text/format_values.h" // Ui::FormatPhone diff --git a/Telegram/SourceFiles/mainwidget.cpp b/Telegram/SourceFiles/mainwidget.cpp index fb4b8fe91..66dc1c3a4 100644 --- a/Telegram/SourceFiles/mainwidget.cpp +++ b/Telegram/SourceFiles/mainwidget.cpp @@ -62,7 +62,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "dialogs/dialogs_key.h" #include "history/history.h" #include "history/history_widget.h" -#include "history/history_message.h" +#include "history/history_item.h" +#include "history/history_item_helpers.h" // GetErrorTextForSending. #include "history/view/media/history_view_media.h" #include "history/view/history_view_service_message.h" #include "history/view/history_view_element.h" diff --git a/Telegram/SourceFiles/media/player/media_player_widget.cpp b/Telegram/SourceFiles/media/player/media_player_widget.cpp index 772577ceb..f43351253 100644 --- a/Telegram/SourceFiles/media/player/media_player_widget.cpp +++ b/Telegram/SourceFiles/media/player/media_player_widget.cpp @@ -34,6 +34,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "styles/style_media_player.h" #include "styles/style_media_view.h" #include "history/history_item.h" +#include "history/history_item_helpers.h" #include "storage/storage_account.h" #include "main/main_session.h" diff --git a/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp b/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp index a2ad42ffd..3dc8fe652 100644 --- a/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp +++ b/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp @@ -46,7 +46,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "media/streaming/media_streaming_player.h" #include "media/player/media_player_instance.h" #include "history/history.h" -#include "history/history_message.h" +#include "history/history_item.h" +#include "history/history_item_helpers.h" #include "history/view/media/history_view_media.h" #include "data/data_media_types.h" #include "data/data_session.h" diff --git a/Telegram/SourceFiles/overview/overview_layout.cpp b/Telegram/SourceFiles/overview/overview_layout.cpp index 5a60b599b..64412a4f0 100644 --- a/Telegram/SourceFiles/overview/overview_layout.cpp +++ b/Telegram/SourceFiles/overview/overview_layout.cpp @@ -35,6 +35,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/history.h" #include "history/history_item.h" #include "history/history_item_components.h" +#include "history/history_item_helpers.h" #include "history/view/history_view_cursor_state.h" #include "history/view/media/history_view_document.h" // DrawThumbnailAsSongCover #include "base/unixtime.h" @@ -644,7 +645,7 @@ Voice::Voice( lt_duration, { .text = Ui::FormatDurationText(duration()) }, Ui::Text::WithEntities)); - _details.setLink(1, goToMessageClickHandler(parent)); + _details.setLink(1, JumpToMessageClickHandler(parent)); } void Voice::initDimensions() { @@ -944,7 +945,9 @@ Document::Document( const style::OverviewFileLayout &st) : RadialProgressItem(delegate, parent) , _data(fields.document) -, _msgl(parent->isHistoryEntry() ? goToMessageClickHandler(parent) : nullptr) +, _msgl(parent->isHistoryEntry() + ? JumpToMessageClickHandler(parent) + : nullptr) , _namel(std::make_shared( _data, crl::guard(this, [=](FullMsgId id) { diff --git a/Telegram/SourceFiles/payments/payments_form.cpp b/Telegram/SourceFiles/payments/payments_form.cpp index fa423d8a0..248f09caf 100644 --- a/Telegram/SourceFiles/payments/payments_form.cpp +++ b/Telegram/SourceFiles/payments/payments_form.cpp @@ -16,7 +16,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_file_origin.h" #include "countries/countries_instance.h" #include "history/history_item.h" -#include "history/history_service.h" // HistoryServicePayment. +#include "history/history_item_components.h" #include "stripe/stripe_api_client.h" #include "stripe/stripe_error.h" #include "stripe/stripe_token.h" diff --git a/Telegram/SourceFiles/settings/settings_privacy_controllers.cpp b/Telegram/SourceFiles/settings/settings_privacy_controllers.cpp index 2cb3ddbe6..8a85b73c6 100644 --- a/Telegram/SourceFiles/settings/settings_privacy_controllers.cpp +++ b/Telegram/SourceFiles/settings/settings_privacy_controllers.cpp @@ -21,7 +21,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/view/history_view_element.h" #include "history/view/history_view_message.h" #include "history/history_item_components.h" -#include "history/history_message.h" +#include "history/history_item.h" #include "history/history.h" #include "calls/calls_instance.h" #include "base/unixtime.h" @@ -188,7 +188,7 @@ AdminLog::OwnedItem GenerateForwardedItem( history->nextNonHistoryEntryId(), data, MessageFlag::FakeHistoryItem); - }, [](auto &&) -> not_null { + }, [](auto &&) -> not_null { Unexpected("Type in GenerateForwardedItem."); }); diff --git a/Telegram/SourceFiles/support/support_autocomplete.cpp b/Telegram/SourceFiles/support/support_autocomplete.cpp index b9c41b8f4..6561b8bfd 100644 --- a/Telegram/SourceFiles/support/support_autocomplete.cpp +++ b/Telegram/SourceFiles/support/support_autocomplete.cpp @@ -18,7 +18,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "support/support_common.h" #include "history/view/history_view_message.h" #include "history/view/history_view_service_message.h" -#include "history/history_message.h" +#include "history/history_item.h" #include "lang/lang_keys.h" #include "base/unixtime.h" #include "base/call_delayed.h" diff --git a/Telegram/SourceFiles/window/window_peer_menu.cpp b/Telegram/SourceFiles/window/window_peer_menu.cpp index 43ab08b5e..3ab37825b 100644 --- a/Telegram/SourceFiles/window/window_peer_menu.cpp +++ b/Telegram/SourceFiles/window/window_peer_menu.cpp @@ -54,8 +54,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "api/api_updates.h" #include "mtproto/mtproto_config.h" #include "history/history.h" -#include "history/history_item.h" -#include "history/history_message.h" // GetErrorTextForSending. +#include "history/history_item_helpers.h" // GetErrorTextForSending. #include "history/view/history_view_context_menu.h" #include "window/window_adaptive.h" // Adaptive::isThreeColumn #include "window/window_session_controller.h"