tdesktop/Telegram/SourceFiles/history/history_message.cpp

1875 lines
52 KiB
C++

/*
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_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_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<PeerData*> peer,
PeerId from,
not_null<HistoryItem*> 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<const HistoryItem*> 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<PeerData*> peer,
const HistoryItemsList &items,
const TextWithTags &comment,
bool ignoreSlowmodeCountdown) {
if (!peer->canWrite()) {
return tr::lng_forward_cant(tr::now);
}
for (const auto &item : items) {
if (const auto media = item->media()) {
const auto error = media->errorTextForForward(peer);
if (!error.isEmpty() && error != qstr("skip")) {
return error;
}
}
}
const auto error = Data::RestrictionError(
peer,
ChatRestriction::SendInline);
if (error && HasInlineItems(items)) {
return *error;
}
if (peer->slowmodeApplied()) {
if (const auto history = peer->owner().historyLoaded(peer)) {
if (!ignoreSlowmodeCountdown
&& (history->latestSendingMessage() != nullptr)
&& (!items.empty() || !comment.text.isEmpty())) {
return tr::lng_slowmode_no_many(tr::now);
}
}
if (comment.text.size() > MaxMessageSize) {
return tr::lng_slowmode_too_long(tr::now);
} else if (!items.empty() && !comment.text.isEmpty()) {
return tr::lng_slowmode_no_many(tr::now);
} else if (items.size() > 1) {
const auto albumForward = [&] {
if (const auto groupId = items.front()->groupId()) {
for (const auto &item : items) {
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 (!ignoreSlowmodeCountdown) {
return tr::lng_slowmode_enabled(
tr::now,
lt_left,
Ui::FormatDurationWordsSlowmode(left));
}
}
return QString();
}
void RequestDependentMessageData(
not_null<HistoryItem*> item,
PeerId peerId,
MsgId msgId) {
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<PeerData*> peer) {
return MessageFlag::BeingSent
| (peer->isSelf() ? MessageFlag() : MessageFlag::Outgoing);
}
bool ShouldSendSilent(
not_null<PeerData*> peer,
const Api::SendOptions &options) {
return options.silent
|| (peer->isBroadcast()
&& peer->owner().notifySettings().silentPosts(peer))
|| (peer->session().supportMode()
&& peer->session().settings().supportAllSilent());
}
MsgId LookupReplyToTop(not_null<History*> history, MsgId replyToId) {
const auto &owner = history->owner();
if (const auto item = owner.message(history->peer, replyToId)) {
return item->replyToTop();
}
return 0;
}
MTPMessageReplyHeader NewMessageReplyHeader(const Api::SendAction &action) {
if (const auto id = action.replyTo) {
if (const auto replyToTop = LookupReplyToTop(action.history, id)) {
return MTP_messageReplyHeader(
MTP_flags(MTPDmessageReplyHeader::Flag::f_reply_to_top_id),
MTP_int(id),
MTPPeer(),
MTP_int(replyToTop));
}
return MTP_messageReplyHeader(
MTP_flags(0),
MTP_int(id),
MTPPeer(),
MTPint());
}
return MTPMessageReplyHeader();
}
QString GetErrorTextForSending(
not_null<PeerData*> peer,
const HistoryItemsList &items,
bool ignoreSlowmodeCountdown) {
return GetErrorTextForSending(peer, items, {}, ignoreSlowmodeCountdown);
}
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;
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*> 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(
data.vreply_to_msg_id().v);
});
}
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*> 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) {
config.replyTo = data.vreply_to_msg_id().v;
config.replyToTop = data.vreply_to_top_id().value_or(
data.vreply_to_msg_id().v);
}
});
}
createComponents(std::move(config));
data.vaction().match([&](const MTPDmessageActionPhoneCall &data) {
_media = std::make_unique<Data::MediaCall>(
this,
Data::ComputeCallData(data));
setTextValue({});
}, [](const auto &) {
Unexpected("Service message action type in HistoryMessage.");
});
applyTTL(data);
}
HistoryMessage::HistoryMessage(
not_null<History*> history,
MsgId id,
MessageFlags flags,
TimeId date,
PeerId from,
const QString &postAuthor,
not_null<HistoryItem*> original)
: 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();
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*> 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*> history,
MsgId id,
MessageFlags flags,
MsgId replyTo,
UserId viaBotId,
TimeId date,
PeerId from,
const QString &postAuthor,
not_null<DocumentData*> 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<Data::MediaFile>(
this,
document,
skipPremiumEffect);
setText(caption);
}
HistoryMessage::HistoryMessage(
not_null<History*> history,
MsgId id,
MessageFlags flags,
MsgId replyTo,
UserId viaBotId,
TimeId date,
PeerId from,
const QString &postAuthor,
not_null<PhotoData*> 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<Data::MediaPhoto>(this, photo);
setText(caption);
}
HistoryMessage::HistoryMessage(
not_null<History*> history,
MsgId id,
MessageFlags flags,
MsgId replyTo,
UserId viaBotId,
TimeId date,
PeerId from,
const QString &postAuthor,
not_null<GameData*> 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<Data::MediaGame>(this, game);
setTextValue({});
}
HistoryMessage::HistoryMessage(
not_null<History*> history,
MsgId id,
Data::SponsoredFrom from,
const TextWithEntities &textWithEntities)
: HistoryItem(
history,
id,
((history->peer->isChannel() ? MessageFlag::Post : MessageFlag(0))
//| (from.peer ? MessageFlag::HasFromId : MessageFlag(0))
| MessageFlag::Local),
HistoryItem::NewMessageDate(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 replyToTop = LookupReplyToTop(history(), replyTo);
config.replyToTop = replyToTop ? replyToTop : replyTo;
}
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<HistoryMessageViews>()) {
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<HistoryMessageViews>()) {
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<HistoryMessageViews>()) {
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<HistoryMessageForwarded>()) {
return forwarded->savedFromPeer && forwarded->savedFromMsgId;
}
return false;
}
MsgId HistoryMessage::repliesInboxReadTill() const {
if (const auto views = Get<HistoryMessageViews>()) {
return views->repliesInboxReadTillId;
}
return 0;
}
void HistoryMessage::setRepliesInboxReadTill(
MsgId readTillId,
std::optional<int> unreadCount) {
if (const auto views = Get<HistoryMessageViews>()) {
const auto newReadTillId = std::max(readTillId.bare, int64(1));
const auto ignore = (newReadTillId < views->repliesInboxReadTillId);
if (ignore) {
return;
}
const auto changed = (newReadTillId > views->repliesInboxReadTillId);
if (changed) {
const auto wasUnread = repliesAreComments() && areRepliesUnread();
views->repliesInboxReadTillId = newReadTillId;
if (wasUnread && !areRepliesUnread()) {
history()->owner().requestItemRepaint(this);
}
}
const auto wasUnreadCount = (views->repliesUnreadCount >= 0)
? std::make_optional(views->repliesUnreadCount)
: std::nullopt;
if (unreadCount != wasUnreadCount
&& (changed || unreadCount.has_value())) {
setUnreadRepliesCount(views, unreadCount.value_or(-1));
}
}
}
MsgId HistoryMessage::computeRepliesInboxReadTillFull() const {
const auto views = Get<HistoryMessageViews>();
if (!views) {
return 0;
}
const auto local = views->repliesInboxReadTillId;
const auto group = views->commentsMegagroupId
? history()->owner().historyLoaded(
peerFromChannel(views->commentsMegagroupId))
: history().get();
if (const auto megagroup = group->peer->asChannel()) {
if (megagroup->amIn()) {
return std::max(local, group->inboxReadTillId());
}
}
return local;
}
MsgId HistoryMessage::repliesOutboxReadTill() const {
if (const auto views = Get<HistoryMessageViews>()) {
return views->repliesOutboxReadTillId;
}
return 0;
}
void HistoryMessage::setRepliesOutboxReadTill(MsgId readTillId) {
if (const auto views = Get<HistoryMessageViews>()) {
const auto newReadTillId = std::max(readTillId.bare, int64(1));
if (newReadTillId > views->repliesOutboxReadTillId) {
views->repliesOutboxReadTillId = newReadTillId;
if (!repliesAreComments()) {
history()->session().changes().historyUpdated(
history(),
Data::HistoryUpdate::Flag::OutboxRead);
}
}
}
}
MsgId HistoryMessage::computeRepliesOutboxReadTillFull() const {
const auto views = Get<HistoryMessageViews>();
if (!views) {
return 0;
}
const auto local = views->repliesOutboxReadTillId;
const auto group = views->commentsMegagroupId
? history()->owner().historyLoaded(
peerFromChannel(views->commentsMegagroupId))
: history().get();
if (const auto megagroup = group->peer->asChannel()) {
if (megagroup->amIn()) {
return std::max(local, group->outboxReadTillId());
}
}
return local;
}
void HistoryMessage::setRepliesMaxId(MsgId maxId) {
if (const auto views = Get<HistoryMessageViews>()) {
if (views->repliesMaxId != maxId) {
const auto comments = repliesAreComments();
const auto wasUnread = comments && areRepliesUnread();
views->repliesMaxId = maxId;
if (comments && wasUnread != areRepliesUnread()) {
history()->owner().requestItemRepaint(this);
}
}
}
}
void HistoryMessage::setRepliesPossibleMaxId(MsgId possibleMaxId) {
if (const auto views = Get<HistoryMessageViews>()) {
if (views->repliesMaxId < possibleMaxId) {
const auto comments = repliesAreComments();
const auto wasUnread = comments && areRepliesUnread();
views->repliesMaxId = possibleMaxId;
if (comments && !wasUnread && areRepliesUnread()) {
history()->owner().requestItemRepaint(this);
}
}
}
}
bool HistoryMessage::areRepliesUnread() const {
const auto views = Get<HistoryMessageViews>();
if (!views) {
return false;
}
const auto local = views->repliesInboxReadTillId;
if (views->repliesInboxReadTillId < 2 || views->repliesMaxId <= local) {
return false;
}
const auto group = views->commentsMegagroupId
? history()->owner().historyLoaded(
peerFromChannel(views->commentsMegagroupId))
: history().get();
return !group || (views->repliesMaxId > group->inboxReadTillId());
}
FullMsgId HistoryMessage::commentsItemId() const {
if (const auto views = Get<HistoryMessageViews>()) {
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<HistoryMessageReply>()) {
reply->replyToMsgTop = id.msg;
}
}
} else if (const auto views = Get<HistoryMessageViews>()) {
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<HistoryMessageReply>()) {
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<HistoryMessageReply>()) {
reply->replyToPeerId = config.replyToPeer;
reply->replyToMsgId = config.replyTo;
reply->replyToMsgTop = isScheduled() ? 0 : config.replyToTop;
if (!reply->updateData(this)) {
RequestDependentMessageData(
this,
reply->replyToPeerId,
reply->replyToMsgId);
}
}
if (const auto via = Get<HistoryMessageVia>()) {
via->create(&history()->owner(), config.viaBotId);
}
if (const auto views = Get<HistoryMessageViews>()) {
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<HistoryMessageEdited>()) {
edited->date = config.editDate;
}
if (const auto msgsigned = Get<HistoryMessageSigned>()) {
msgsigned->author = config.author.isEmpty()
? config.authorOriginal
: config.author;
msgsigned->isAnonymousRank = !isDiscussionPost()
&& author()->isMegagroup();
}
setupForwardedComponent(config);
if (const auto markup = Get<HistoryMessageReplyMarkup>()) {
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<HistoryMessageForwarded>();
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<HiddenSenderInfo>(
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<HistoryMessageViews>()) {
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<Data::Media> HistoryMessage::CreateMedia(
not_null<HistoryMessage*> item,
const MTPMessageMedia &media) {
using Result = std::unique_ptr<Data::Media>;
return media.match([&](const MTPDmessageMediaContact &media) -> Result {
return std::make_unique<Data::MediaContact>(
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<Data::MediaLocation>(
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<Data::MediaLocation>(
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<Data::MediaLocation>(
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<Data::MediaPhoto>(
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<Data::MediaFile>(
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<Data::MediaWebPage>(
item,
item->history()->owner().processWebpage(webpage));
}, [&](const MTPDwebPage &webpage) -> Result {
return std::make_unique<Data::MediaWebPage>(
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<Data::MediaGame>(
item,
item->history()->owner().processGame(game));
});
}, [&](const MTPDmessageMediaInvoice &media) -> Result {
return std::make_unique<Data::MediaInvoice>(
item,
Data::ComputeInvoiceData(item, media));
}, [&](const MTPDmessageMediaPoll &media) -> Result {
return std::make_unique<Data::MediaPoll>(
item,
item->history()->owner().processPoll(media));
}, [&](const MTPDmessageMediaDice &media) -> Result {
return std::make_unique<Data::MediaDice>(
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<HistoryMessageEdited>()) {
AddComponents(HistoryMessageEdited::Bit());
}
auto edited = Get<HistoryMessageEdited>();
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<HistoryMessageForwarded>();
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;
}
if (isUnreadMention()) {
if (history()->unreadMentions().add(id, type)) {
history()->session().changes().historyUpdated(
history(),
Data::HistoryUpdate::Flag::UnreadMentions);
}
}
if (hasUnreadReaction()) {
if (history()->unreadReactions().add(id, type)) {
if (type == HistoryUnreadThings::AddType::New) {
history()->session().changes().messageUpdated(
this,
Data::MessageUpdate::Flag::NewUnreadReaction);
}
if (hasUnreadReaction()) {
history()->session().changes().historyUpdated(
history(),
Data::HistoryUpdate::Flag::UnreadReactions);
}
}
}
}
void HistoryMessage::destroyHistoryEntry() {
if (isUnreadMention()) {
history()->unreadMentions().erase(id);
}
if (hasUnreadReaction()) {
history()->unreadReactions().erase(id);
}
if (const auto reply = Get<HistoryMessageReply>()) {
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<HistoryMessageReply>()) {
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;
}
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<HistoryMessageReplyMarkup>()) {
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<HistoryMessageReplyMarkup>()) {
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<HistoryMessageReplyMarkup>()) {
AddComponents(HistoryMessageReplyMarkup::Bit());
}
Get<HistoryMessageReplyMarkup>()->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<HistoryMessageViews>();
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) {
}
void HistoryMessage::setPostAuthor(const QString &author) {
auto msgsigned = Get<HistoryMessageSigned>();
if (author.isEmpty()) {
if (!msgsigned) {
return;
}
RemoveComponents(HistoryMessageSigned::Bit());
history()->owner().requestItemResize(this);
return;
}
if (!msgsigned) {
AddComponents(HistoryMessageSigned::Bit());
msgsigned = Get<HistoryMessageSigned>();
} 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<HistoryMessageViews>();
if (!views) {
AddComponents(HistoryMessageViews::Bit());
views = Get<HistoryMessageViews>();
}
const auto &repliers = data.recentRepliers;
const auto count = data.repliesCount;
const auto channelId = data.channelId;
const auto readTillId = data.readMaxId
? std::max({
views->repliesInboxReadTillId.bare,
data.readMaxId.bare,
int64(1),
})
: views->repliesInboxReadTillId;
const auto maxId = data.maxId ? data.maxId : views->repliesMaxId;
const auto countsChanged = (views->replies.count != count)
|| (views->repliesInboxReadTillId != readTillId)
|| (views->repliesMaxId != 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;
}
views->commentsMegagroupId = channelId;
const auto wasUnread = channelId && areRepliesUnread();
views->repliesInboxReadTillId = readTillId;
views->repliesMaxId = maxId;
if (channelId && wasUnread != areRepliesUnread()) {
history()->owner().requestItemRepaint(this);
}
refreshRepliesText(views, megagroupChanged);
}
void HistoryMessage::clearReplies() {
auto views = Get<HistoryMessageViews>();
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<HistoryMessageViews*> 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,
std::optional<bool> unread) {
const auto views = Get<HistoryMessageViews>();
const auto limit = HistoryMessageViews::kMaxRecentRepliers;
if (!views) {
return;
}
// Update unread count.
if (!unread) {
setUnreadRepliesCount(views, -1);
} else if (views->repliesUnreadCount >= 0 && *unread) {
setUnreadRepliesCount(
views,
std::max(views->repliesUnreadCount + delta, 0));
}
// 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::setUnreadRepliesCount(
not_null<HistoryMessageViews*> views,
int count) {
// Track unread count in discussion forwards, not in the channel posts.
if (views->repliesUnreadCount == count || views->commentsMegagroupId) {
return;
}
views->repliesUnreadCount = count;
history()->session().changes().messageUpdated(
this,
Data::MessageUpdate::Flag::RepliesUnreadCount);
}
void HistoryMessage::setSponsoredFrom(const Data::SponsoredFrom &from) {
AddComponents(HistoryMessageSponsored::Bit());
const auto sponsored = Get<HistoryMessageSponsored>();
sponsored->sender = std::make_unique<HiddenSenderInfo>(
from.title,
false);
sponsored->recommended = from.isRecommended;
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::setReplyToTop(MsgId replyToTop) {
const auto reply = Get<HistoryMessageReply>();
if (!reply
|| (reply->replyToMsgTop == replyToTop)
|| (reply->replyToMsgTop != 0)
|| isScheduled()) {
return;
}
reply->replyToMsgTop = replyToTop;
changeReplyToTopCounter(reply, 1);
}
void HistoryMessage::setRealId(MsgId newId) {
HistoryItem::setRealId(newId);
history()->owner().groups().refreshMessage(this);
history()->owner().requestItemResize(this);
if (const auto reply = Get<HistoryMessageReply>()) {
if (reply->replyToLink()) {
reply->setReplyToLinkFrom(this);
}
changeReplyToTopCounter(reply, 1);
}
}
void HistoryMessage::incrementReplyToTopCounter() {
if (const auto reply = Get<HistoryMessageReply>()) {
changeReplyToTopCounter(reply, 1);
}
}
void HistoryMessage::changeReplyToTopCounter(
not_null<HistoryMessageReply*> reply,
int delta) {
if (!isRegular() || !reply->replyToTop()) {
return;
}
const auto peerId = _history->peer->id;
if (!peerIsChannel(peerId)) {
return;
}
const auto top = _history->owner().message(peerId, reply->replyToTop());
if (!top) {
return;
}
auto unread = out() ? std::make_optional(false) : std::nullopt;
if (const auto views = top->Get<HistoryMessageViews>()) {
if (views->commentsMegagroupId) {
// This is a post in channel, we don't track its replies.
return;
}
if (views->repliesInboxReadTillId > 0) {
unread = !out() && (id > views->repliesInboxReadTillId);
}
}
const auto changeFor = [&](not_null<HistoryItem*> item) {
if (const auto from = displayFrom()) {
item->changeRepliesCount(delta, from->id, unread);
} else {
item->changeRepliesCount(delta, PeerId(), unread);
}
};
changeFor(top);
if (const auto original = top->lookupDiscussionPostOriginal()) {
changeFor(original);
}
}
void HistoryMessage::dependencyItemRemoved(HistoryItem *dependency) {
if (const auto reply = Get<HistoryMessageReply>()) {
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<HistoryView::Element> HistoryMessage::createView(
not_null<HistoryView::ElementDelegate*> delegate,
HistoryView::Element *replacing) {
return delegate->elementCreate(this, replacing);
}
HistoryMessage::~HistoryMessage() {
_media.reset();
clearSavedMedia();
if (auto reply = Get<HistoryMessageReply>()) {
reply->clearData(this);
}
}