/* 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 "mainwidget.h" #include "mainwindow.h" #include "apiwrap.h" #include "history/history.h" #include "history/history_item_components.h" #include "history/history_location_manager.h" #include "history/history_media_types.h" #include "history/history_service.h" #include "history/view/history_view_service_message.h" #include "auth_session.h" #include "boxes/share_box.h" #include "boxes/confirm_box.h" #include "ui/toast/toast.h" #include "ui/text_options.h" #include "messenger.h" #include "layout.h" #include "window/notifications_manager.h" #include "window/window_controller.h" #include "observer_peer.h" #include "storage/storage_shared_media.h" #include "data/data_session.h" #include "data/data_media_types.h" #include "styles/style_dialogs.h" #include "styles/style_widgets.h" #include "styles/style_history.h" #include "styles/style_window.h" namespace { constexpr auto kPinnedMessageTextLimit = 16; MTPDmessage::Flags NewForwardedFlags( not_null peer, UserId from, not_null fwd) { auto result = NewMessageFlags(peer) | MTPDmessage::Flag::f_fwd_from; if (from) { result |= MTPDmessage::Flag::f_from_id; } if (fwd->Has()) { result |= MTPDmessage::Flag::f_via_bot_id; } if (const auto channel = peer->asChannel()) { if (dynamic_cast(fwd->media())) { // Drop web page if we're not allowed to send it. if (channel->restricted( ChannelRestriction::f_embed_links)) { result &= ~MTPDmessage::Flag::f_media; } } } else if (const auto media = fwd->media()) { if (media->forwardedBecomesUnread()) { result |= MTPDmessage::Flag::f_media_unread; } } if (fwd->hasViews()) { result |= MTPDmessage::Flag::f_views; } return result; } bool HasInlineItems(const HistoryItemsList &items) { for (const auto item : items) { if (item->viaBot()) { return true; } } return false; } } // namespace void FastShareMessage(not_null item) { struct ShareData { ShareData(not_null peer, MessageIdsList &&ids) : peer(peer) , msgIds(std::move(ids)) { } not_null peer; MessageIdsList msgIds; base::flat_set requests; }; const auto data = std::make_shared( item->history()->peer, Auth().data().itemOrItsGroup(item)); const auto isGroup = (Auth().data().groups().find(item) != nullptr); const auto isGame = item->getMessageBot() && item->media() && (item->media()->game() != nullptr); const auto canCopyLink = item->hasDirectLink() || isGame; auto copyCallback = [data]() { if (auto main = App::main()) { if (auto item = App::histItemById(data->msgIds[0])) { if (item->hasDirectLink()) { QApplication::clipboard()->setText(item->directLink()); Ui::Toast::Show(lang(lng_channel_public_link_copied)); } else if (const auto bot = item->getMessageBot()) { if (const auto media = item->media()) { if (const auto game = media->game()) { const auto link = Messenger::Instance().createInternalLinkFull( bot->username + qsl("?game=") + game->shortName); QApplication::clipboard()->setText(link); Ui::Toast::Show(lang(lng_share_game_link_copied)); } } } } } }; auto submitCallback = [data, isGroup](const QVector &result) { if (!data->requests.empty()) { return; // Share clicked already. } auto items = Auth().data().idsToItems(data->msgIds); if (items.empty() || result.empty()) { return; } auto restrictedSomewhere = false; auto restrictedEverywhere = true; auto firstError = QString(); for (const auto peer : result) { const auto error = GetErrorTextForForward(peer, items); if (!error.isEmpty()) { if (firstError.isEmpty()) { firstError = error; } restrictedSomewhere = true; continue; } restrictedEverywhere = false; } if (restrictedEverywhere) { Ui::show( Box(firstError), LayerOption::KeepOther); return; } auto doneCallback = [data](const MTPUpdates &updates, mtpRequestId requestId) { if (auto main = App::main()) { main->sentUpdatesReceived(updates); } data->requests.remove(requestId); if (data->requests.empty()) { Ui::Toast::Show(lang(lng_share_done)); Ui::hideLayer(); } }; const auto sendFlags = MTPmessages_ForwardMessages::Flag(0) | MTPmessages_ForwardMessages::Flag::f_with_my_score | (isGroup ? MTPmessages_ForwardMessages::Flag::f_grouped : MTPmessages_ForwardMessages::Flag(0)); auto msgIds = QVector(); msgIds.reserve(data->msgIds.size()); for (const auto fullId : data->msgIds) { msgIds.push_back(MTP_int(fullId.msg)); } auto generateRandom = [&] { auto result = QVector(data->msgIds.size()); for (auto &value : result) { value = rand_value(); } return result; }; if (auto main = App::main()) { for (const auto peer : result) { if (!GetErrorTextForForward(peer, items).isEmpty()) { continue; } auto request = MTPmessages_ForwardMessages( MTP_flags(sendFlags), data->peer->input, MTP_vector(msgIds), MTP_vector(generateRandom()), peer->input); auto callback = doneCallback; auto requestId = MTP::send(request, rpcDone(std::move(callback))); data->requests.insert(requestId); } } }; auto filterCallback = [isGame](PeerData *peer) { if (peer->canWrite()) { if (auto channel = peer->asChannel()) { return isGame ? (!channel->isBroadcast()) : true; } return true; } return false; }; auto copyLinkCallback = canCopyLink ? base::lambda(std::move(copyCallback)) : base::lambda(); Ui::show(Box( std::move(copyLinkCallback), std::move(submitCallback), std::move(filterCallback))); } base::lambda HistoryDependentItemCallback( const FullMsgId &msgId) { return [dependent = msgId](ChannelData *channel, MsgId msgId) { if (auto item = App::histItemById(dependent)) { item->updateDependencyItem(); } }; } MTPDmessage::Flags NewMessageFlags(not_null peer) { MTPDmessage::Flags result = 0; if (!peer->isSelf()) { result |= MTPDmessage::Flag::f_out; //if (p->isChat() || (p->isUser() && !p->asUser()->botInfo)) { // result |= MTPDmessage::Flag::f_unread; //} } return result; } QString GetErrorTextForForward( not_null peer, const HistoryItemsList &items) { if (!peer->canWrite()) { return lang(lng_forward_cant); } if (auto megagroup = peer->asMegagroup()) { for (const auto item : items) { if (const auto media = item->media()) { const auto error = media->errorTextForForward(megagroup); if (!error.isEmpty() && error != qstr("skip")) { return error; } } } if (megagroup->restricted(ChannelRestriction::f_send_inline) && HasInlineItems(items)) { return lang(lng_restricted_send_inline); } } return QString(); } struct HistoryMessage::CreateConfig { MsgId replyTo = 0; UserId viaBotId = 0; int viewsCount = -1; QString author; PeerId senderOriginal = 0; MsgId originalId = 0; PeerId savedFromPeer = 0; MsgId savedFromMsgId = 0; QString authorOriginal; QDateTime originalDate; QDateTime editDate; // For messages created from MTP structs. const MTPReplyMarkup *mtpMarkup = nullptr; // For messages created from existing messages (forwarded). const HistoryMessageReplyMarkup *inlineMarkup = nullptr; }; HistoryMessage::HistoryMessage( not_null history, const MTPDmessage &msg) : HistoryItem(history, msg.vid.v, msg.vflags.v, ::date(msg.vdate), msg.has_from_id() ? msg.vfrom_id.v : 0) { CreateConfig config; if (msg.has_fwd_from() && msg.vfwd_from.type() == mtpc_messageFwdHeader) { auto &f = msg.vfwd_from.c_messageFwdHeader(); config.originalDate = ::date(f.vdate); if (f.has_from_id() || f.has_channel_id()) { config.senderOriginal = f.has_channel_id() ? peerFromChannel(f.vchannel_id) : peerFromUser(f.vfrom_id); if (f.has_channel_post()) config.originalId = f.vchannel_post.v; if (f.has_post_author()) config.authorOriginal = qs(f.vpost_author); if (f.has_saved_from_peer() && f.has_saved_from_msg_id()) { config.savedFromPeer = peerFromMTP(f.vsaved_from_peer); config.savedFromMsgId = f.vsaved_from_msg_id.v; } } } if (msg.has_reply_to_msg_id()) config.replyTo = msg.vreply_to_msg_id.v; if (msg.has_via_bot_id()) config.viaBotId = msg.vvia_bot_id.v; if (msg.has_views()) config.viewsCount = msg.vviews.v; if (msg.has_reply_markup()) config.mtpMarkup = &msg.vreply_markup; if (msg.has_edit_date()) config.editDate = ::date(msg.vedit_date); if (msg.has_post_author()) config.author = qs(msg.vpost_author); if (msg.has_grouped_id()) { setGroupId(MessageGroupId::FromRaw(msg.vgrouped_id.v)); } createComponents(config); if (msg.has_media()) { setMedia(msg.vmedia); } auto text = TextUtilities::Clean(qs(msg.vmessage)); auto entities = msg.has_entities() ? TextUtilities::EntitiesFromMTP(msg.ventities.v) : EntitiesInText(); setText({ text, entities }); } HistoryMessage::HistoryMessage( not_null history, const MTPDmessageService &msg) : HistoryItem(history, msg.vid.v, mtpCastFlags(msg.vflags.v), ::date(msg.vdate), msg.has_from_id() ? msg.vfrom_id.v : 0) { CreateConfig config; if (msg.has_reply_to_msg_id()) config.replyTo = msg.vreply_to_msg_id.v; createComponents(config); switch (msg.vaction.type()) { case mtpc_messageActionPhoneCall: { _media = std::make_unique( this, msg.vaction.c_messageActionPhoneCall()); } break; default: Unexpected("Service message action type in HistoryMessage."); } setText(TextWithEntities {}); } HistoryMessage::HistoryMessage( not_null history, MsgId id, MTPDmessage::Flags flags, QDateTime date, UserId from, const QString &postAuthor, not_null fwd) : HistoryItem(history, id, NewForwardedFlags(history->peer, from, fwd) | flags, date, from) { CreateConfig config; if (fwd->Has() || !fwd->history()->peer->isSelf()) { // Server doesn't add "fwd_from" to non-forwarded messages from chat with yourself. config.originalDate = fwd->dateOriginal(); auto senderOriginal = fwd->senderOriginal(); config.senderOriginal = senderOriginal->id; config.authorOriginal = fwd->authorOriginal(); if (senderOriginal->isChannel()) { config.originalId = fwd->idOriginal(); } } if (history->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 = fwd->history()->peer->id; config.savedFromMsgId = fwd->id; //} } if (flags & MTPDmessage::Flag::f_post_author) { config.author = postAuthor; } auto fwdViaBot = fwd->viaBot(); if (fwdViaBot) config.viaBotId = peerToUser(fwdViaBot->id); int fwdViewsCount = fwd->viewsCount(); if (fwdViewsCount > 0) { config.viewsCount = fwdViewsCount; } else if (isPost()) { config.viewsCount = 1; } // Copy inline keyboard when forwarding messages with a game. auto mediaOriginal = fwd->media(); if (mediaOriginal && mediaOriginal->game()) { config.inlineMarkup = fwd->inlineReplyMarkup(); } createComponents(config); auto ignoreMedia = [&] { if (mediaOriginal && mediaOriginal->webpage()) { if (const auto channel = history->peer->asChannel()) { if (channel->restricted(ChannelRestriction::f_embed_links)) { return true; } } } return false; }; if (mediaOriginal && !ignoreMedia()) { _media = mediaOriginal->clone(this); } setText(fwd->originalText()); } HistoryMessage::HistoryMessage( not_null history, MsgId id, MTPDmessage::Flags flags, MsgId replyTo, UserId viaBotId, QDateTime date, UserId from, const QString &postAuthor, const TextWithEntities &textWithEntities) : HistoryItem(history, id, flags, date, (flags & MTPDmessage::Flag::f_from_id) ? from : 0) { createComponentsHelper(flags, replyTo, viaBotId, postAuthor, MTPnullMarkup); setText(textWithEntities); } HistoryMessage::HistoryMessage( not_null history, MsgId msgId, MTPDmessage::Flags flags, MsgId replyTo, UserId viaBotId, QDateTime date, UserId from, const QString &postAuthor, not_null document, const QString &caption, const MTPReplyMarkup &markup) : HistoryItem(history, msgId, flags, date, (flags & MTPDmessage::Flag::f_from_id) ? from : 0) { createComponentsHelper(flags, replyTo, viaBotId, postAuthor, markup); _media = std::make_unique(this, document, caption); setText(TextWithEntities()); } HistoryMessage::HistoryMessage( not_null history, MsgId msgId, MTPDmessage::Flags flags, MsgId replyTo, UserId viaBotId, QDateTime date, UserId from, const QString &postAuthor, not_null photo, const QString &caption, const MTPReplyMarkup &markup) : HistoryItem(history, msgId, flags, date, (flags & MTPDmessage::Flag::f_from_id) ? from : 0) { createComponentsHelper(flags, replyTo, viaBotId, postAuthor, markup); _media = std::make_unique(this, photo, caption); setText(TextWithEntities()); } HistoryMessage::HistoryMessage( not_null history, MsgId msgId, MTPDmessage::Flags flags, MsgId replyTo, UserId viaBotId, QDateTime date, UserId from, const QString &postAuthor, not_null game, const MTPReplyMarkup &markup) : HistoryItem(history, msgId, flags, date, (flags & MTPDmessage::Flag::f_from_id) ? from : 0) { createComponentsHelper(flags, replyTo, viaBotId, postAuthor, markup); _media = std::make_unique(this, game); setText(TextWithEntities()); } void HistoryMessage::createComponentsHelper( MTPDmessage::Flags flags, MsgId replyTo, UserId viaBotId, const QString &postAuthor, const MTPReplyMarkup &markup) { CreateConfig config; if (flags & MTPDmessage::Flag::f_via_bot_id) config.viaBotId = viaBotId; if (flags & MTPDmessage::Flag::f_reply_to_msg_id) config.replyTo = replyTo; if (flags & MTPDmessage::Flag::f_reply_markup) config.mtpMarkup = &markup; if (flags & MTPDmessage::Flag::f_post_author) config.author = postAuthor; if (isPost()) config.viewsCount = 1; createComponents(config); } int HistoryMessage::viewsCount() const { if (const auto views = Get()) { return views->_views; } return HistoryItem::viewsCount(); } not_null HistoryMessage::displayFrom() const { return history()->peer->isSelf() ? senderOriginal() : author(); } bool HistoryMessage::updateDependencyItem() { if (const auto reply = Get()) { return reply->updateData(this, true); } return true; } void HistoryMessage::updateAdminBadgeState() { auto hasAdminBadge = [&] { if (auto channel = history()->peer->asChannel()) { if (auto user = author()->asUser()) { return channel->isGroupAdmin(user); } } return false; }(); if (hasAdminBadge) { _flags |= MTPDmessage_ClientFlag::f_has_admin_badge; } else { _flags &= ~MTPDmessage_ClientFlag::f_has_admin_badge; } } void HistoryMessage::applyGroupAdminChanges( const base::flat_map &changes) { auto i = changes.find(peerToUser(author()->id)); if (i != changes.end()) { if (i->second) { _flags |= MTPDmessage_ClientFlag::f_has_admin_badge; } else { _flags &= ~MTPDmessage_ClientFlag::f_has_admin_badge; } Auth().data().requestItemViewResize(this); } } bool HistoryMessage::allowsForward() const { if (id < 0 || isLogEntry()) { return false; } return !_media || _media->allowsForward(); } bool HistoryMessage::allowsEdit(const QDateTime &now) const { const auto peer = _history->peer; const auto messageToMyself = peer->isSelf(); const auto canPinInMegagroup = [&] { if (const auto megagroup = peer->asMegagroup()) { return megagroup->canPinMessages(); } return false; }(); const auto messageTooOld = (messageToMyself || canPinInMegagroup) ? false : (date.secsTo(now) >= Global::EditTimeLimit()); if (id < 0 || messageTooOld) { return false; } if (Has() || Has()) { return false; } if (_media && !_media->allowsEdit()) { return false; } if (messageToMyself) { return true; } if (const auto channel = _history->peer->asChannel()) { if (isPost() && channel->canEditMessages()) { return true; } if (out()) { return !isPost() || channel->canPublish(); } } return out(); } bool HistoryMessage::uploading() const { return _media && _media->uploading(); } void HistoryMessage::createComponents(const CreateConfig &config) { uint64 mask = 0; if (config.replyTo) { mask |= HistoryMessageReply::Bit(); } if (config.viaBotId) { mask |= HistoryMessageVia::Bit(); } if (config.viewsCount >= 0) { mask |= HistoryMessageViews::Bit(); } if (!config.author.isEmpty()) { mask |= HistoryMessageSigned::Bit(); } auto hasViaBot = (config.viaBotId != 0); auto hasInlineMarkup = [&config] { if (config.mtpMarkup) { return (config.mtpMarkup->type() == mtpc_replyInlineMarkup); } return (config.inlineMarkup != nullptr); }; if (!config.editDate.isNull()) { mask |= HistoryMessageEdited::Bit(); } if (config.senderOriginal) { mask |= HistoryMessageForwarded::Bit(); } if (config.mtpMarkup) { // optimization: don't create markup component for the case // MTPDreplyKeyboardHide with flags = 0, assume it has f_zero flag if (config.mtpMarkup->type() != mtpc_replyKeyboardHide || config.mtpMarkup->c_replyKeyboardHide().vflags.v != 0) { mask |= HistoryMessageReplyMarkup::Bit(); } } else if (config.inlineMarkup) { mask |= HistoryMessageReplyMarkup::Bit(); } UpdateComponents(mask); if (const auto reply = Get()) { reply->replyToMsgId = config.replyTo; if (!reply->updateData(this)) { Auth().api().requestMessageData( history()->peer->asChannel(), reply->replyToMsgId, HistoryDependentItemCallback(fullId())); } } if (const auto via = Get()) { via->create(config.viaBotId); } if (const auto views = Get()) { views->_views = config.viewsCount; } if (const auto edited = Get()) { edited->date = config.editDate; } if (const auto msgsigned = Get()) { msgsigned->author = config.author; } if (const auto forwarded = Get()) { forwarded->originalDate = config.originalDate; forwarded->originalSender = App::peer(config.senderOriginal); forwarded->originalId = config.originalId; forwarded->originalAuthor = config.authorOriginal; forwarded->savedFromPeer = App::peerLoaded(config.savedFromPeer); forwarded->savedFromMsgId = config.savedFromMsgId; } if (const auto markup = Get()) { if (config.mtpMarkup) { markup->create(*config.mtpMarkup); } else if (config.inlineMarkup) { markup->create(*config.inlineMarkup); } if (markup->flags & MTPDreplyKeyboardMarkup_ClientFlag::f_has_switch_inline_button) { _flags |= MTPDmessage_ClientFlag::f_has_switch_inline_button; } } _fromNameVersion = displayFrom()->nameVersion; } QString FormatViewsCount(int views) { if (views > 999999) { views /= 100000; if (views % 10) { return QString::number(views / 10) + '.' + QString::number(views % 10) + 'M'; } return QString::number(views / 10) + 'M'; } else if (views > 9999) { views /= 100; if (views % 10) { return QString::number(views / 10) + '.' + QString::number(views % 10) + 'K'; } return QString::number(views / 10) + 'K'; } else if (views > 0) { return QString::number(views); } return qsl("1"); } void HistoryMessage::refreshMedia(const MTPMessageMedia *media) { const auto wasGrouped = Auth().data().groups().isGrouped(this); _media = nullptr; if (media) { setMedia(*media); } if (wasGrouped) { Auth().data().groups().refreshMessage(this); } } void HistoryMessage::setMedia(const MTPMessageMedia &media) { _media = CreateMedia(this, media); if (const auto invoice = _media ? _media->invoice() : nullptr) { if (invoice->receiptMsgId) { replaceBuyWithReceiptInMarkup(); } } } std::unique_ptr HistoryMessage::CreateMedia( not_null item, const MTPMessageMedia &media) { switch (media.type()) { case mtpc_messageMediaContact: { const auto &data = media.c_messageMediaContact(); return std::make_unique( item, data.vuser_id.v, qs(data.vfirst_name), qs(data.vlast_name), qs(data.vphone_number)); } break; case mtpc_messageMediaGeo: { const auto &data = media.c_messageMediaGeo().vgeo; if (data.type() == mtpc_geoPoint) { return std::make_unique( item, LocationCoords(data.c_geoPoint())); } } break; case mtpc_messageMediaGeoLive: { const auto &data = media.c_messageMediaGeoLive().vgeo; if (data.type() == mtpc_geoPoint) { return std::make_unique( item, LocationCoords(data.c_geoPoint())); } } break; case mtpc_messageMediaVenue: { const auto &data = media.c_messageMediaVenue(); if (data.vgeo.type() == mtpc_geoPoint) { return std::make_unique( item, LocationCoords(data.vgeo.c_geoPoint()), qs(data.vtitle), qs(data.vaddress)); } } break; case mtpc_messageMediaPhoto: { const auto &data = media.c_messageMediaPhoto(); if (data.has_ttl_seconds()) { LOG(("App Error: " "Unexpected MTPMessageMediaPhoto " "with ttl_seconds in HistoryMessage.")); } else if (data.has_photo() && data.vphoto.type() == mtpc_photo) { return std::make_unique( item, Auth().data().photo(data.vphoto.c_photo()), /*data.has_caption() ? qs(data.vcaption) : */QString()); // #TODO l76 caption } else { LOG(("API Error: " "Got MTPMessageMediaPhoto " "without photo and without ttl_seconds.")); } } break; case mtpc_messageMediaDocument: { const auto &data = media.c_messageMediaDocument(); if (data.has_ttl_seconds()) { LOG(("App Error: " "Unexpected MTPMessageMediaDocument " "with ttl_seconds in HistoryMessage.")); } else if (data.has_document() && data.vdocument.type() == mtpc_document) { return std::make_unique( item, Auth().data().document(data.vdocument.c_document()), /*data.has_caption() ? qs(data.vcaption) :*/ QString()); // #TODO l76 caption } else { LOG(("API Error: " "Got MTPMessageMediaDocument " "without document and without ttl_seconds.")); } } break; case mtpc_messageMediaWebPage: { const auto &data = media.c_messageMediaWebPage().vwebpage; switch (data.type()) { case mtpc_webPageEmpty: break; case mtpc_webPagePending: return std::make_unique( item, Auth().data().webpage(data.c_webPagePending())); break; case mtpc_webPage: return std::make_unique( item, Auth().data().webpage(data.c_webPage())); break; case mtpc_webPageNotModified: LOG(("API Error: " "webPageNotModified is unexpected in message media.")); break; } } break; case mtpc_messageMediaGame: { const auto &data = media.c_messageMediaGame().vgame; if (data.type() == mtpc_game) { return std::make_unique( item, Auth().data().game(data.c_game())); } } break; case mtpc_messageMediaInvoice: { return std::make_unique( item, media.c_messageMediaInvoice()); } break; }; return nullptr; } void HistoryMessage::replaceBuyWithReceiptInMarkup() { if (auto markup = inlineReplyMarkup()) { for (auto &row : markup->rows) { for (auto &button : row) { if (button.type == HistoryMessageMarkupButton::Type::Buy) { button.text = lang(lng_payments_receipt_button); } } } } } int32 HistoryMessage::plainMaxWidth() const { return st::msgPadding.left() + _text.maxWidth() + st::msgPadding.right(); } void HistoryMessage::applyEdition(const MTPDmessage &message) { 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 (message.has_edit_date()) { _flags |= MTPDmessage::Flag::f_edit_date; if (!Has()) { AddComponents(HistoryMessageEdited::Bit()); } auto edited = Get(); edited->date = ::date(message.vedit_date); } TextWithEntities textWithEntities = { qs(message.vmessage), EntitiesInText() }; if (message.has_entities()) { textWithEntities.entities = TextUtilities::EntitiesFromMTP(message.ventities.v); } setText(textWithEntities); setReplyMarkup(message.has_reply_markup() ? (&message.vreply_markup) : nullptr); refreshMedia(message.has_media() ? (&message.vmedia) : nullptr); setViewsCount(message.has_views() ? message.vviews.v : -1); finishEdition(keyboardTop); } void HistoryMessage::applyEdition(const MTPDmessageService &message) { if (message.vaction.type() == mtpc_messageActionHistoryClear) { applyEditionToEmpty(); } } void HistoryMessage::applyEditionToEmpty() { setEmptyText(); refreshMedia(nullptr); setReplyMarkup(nullptr); setViewsCount(-1); finishEditionToEmpty(); } void HistoryMessage::updateSentMedia(const MTPMessageMedia *media) { if (_flags & MTPDmessage_ClientFlag::f_from_inline_bot) { if (!media || !_media || !_media->updateInlineResultMedia(*media)) { refreshMedia(media); } _flags &= ~MTPDmessage_ClientFlag::f_from_inline_bot; } else { if (!media || !_media || !_media->updateSentMedia(*media)) { refreshMedia(media); } } Auth().data().requestItemViewResize(this); } void HistoryMessage::addToUnreadMentions(UnreadMentionType type) { if (IsServerMsgId(id) && mentionsMe() && isMediaUnread()) { if (history()->addToUnreadMentions(id, type)) { Notify::peerUpdatedDelayed( history()->peer, Notify::PeerUpdate::Flag::UnreadMentionsChanged); } } } void HistoryMessage::eraseFromUnreadMentions() { if (mentionsMe() && isMediaUnread()) { history()->eraseFromUnreadMentions(id); } } 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); } return result; } //void HistoryMessage::setMedia(const MTPMessageMedia *media) { // #TODO grouping and games //bool mediaRemovedSkipBlock = false; //if (_media) { // // Don't update Game media because we loose the consumed text of the message. // if (_media->type() == MediaTypeGame) { // return; // } // mediaRemovedSkipBlock = _media->isDisplayed() && _media->isBubbleBottom(); // _media.reset(); //} //refreshMedia(media); //auto mediaDisplayed = _media && _media->isDisplayed(); //if (mediaDisplayed && _media->isBubbleBottom() && !mediaRemovedSkipBlock) { // _text.removeSkipBlock(); // _textWidth = -1; // _textHeight = 0; //} else if (mediaRemovedSkipBlock && (!mediaDisplayed || !_media->isBubbleBottom())) { // _text.updateSkipBlock(skipBlockWidth(), skipBlockHeight()); // _textWidth = -1; // _textHeight = 0; //} //_history->recountGroupingAround(this); //} void HistoryMessage::setText(const TextWithEntities &textWithEntities) { for_const (auto &entity, textWithEntities.entities) { auto type = entity.type(); if (type == EntityInTextUrl || type == EntityInTextCustomUrl || type == EntityInTextEmail) { _flags |= MTPDmessage_ClientFlag::f_has_text_links; break; } } if (_media && _media->consumeMessageText(textWithEntities)) { setEmptyText(); } else { _text.setMarkedText( st::messageTextStyle, textWithEntities, Ui::ItemTextOptions(this)); _textWidth = -1; _textHeight = 0; } } void HistoryMessage::setEmptyText() { _text.setMarkedText( st::messageTextStyle, { QString(), EntitiesInText() }, Ui::ItemTextOptions(this)); _textWidth = -1; _textHeight = 0; } void HistoryMessage::setReplyMarkup(const MTPReplyMarkup *markup) { if (!markup) { if (_flags & MTPDmessage::Flag::f_reply_markup) { _flags &= ~MTPDmessage::Flag::f_reply_markup; if (Has()) { RemoveComponents(HistoryMessageReplyMarkup::Bit()); } Auth().data().requestItemViewResize(this); Notify::replyMarkupUpdated(this); } return; } // optimization: don't create markup component for the case // MTPDreplyKeyboardHide with flags = 0, assume it has f_zero flag if (markup->type() == mtpc_replyKeyboardHide && markup->c_replyKeyboardHide().vflags.v == 0) { bool changed = false; if (Has()) { RemoveComponents(HistoryMessageReplyMarkup::Bit()); changed = true; } if (!(_flags & MTPDmessage::Flag::f_reply_markup)) { _flags |= MTPDmessage::Flag::f_reply_markup; changed = true; } if (changed) { Auth().data().requestItemViewResize(this); Notify::replyMarkupUpdated(this); } } else { if (!(_flags & MTPDmessage::Flag::f_reply_markup)) { _flags |= MTPDmessage::Flag::f_reply_markup; } if (!Has()) { AddComponents(HistoryMessageReplyMarkup::Bit()); } Get()->create(*markup); Auth().data().requestItemViewResize(this); Notify::replyMarkupUpdated(this); } } TextWithEntities HistoryMessage::originalText() const { if (emptyText()) { return { QString(), EntitiesInText() }; } return _text.originalTextWithEntities(); } bool HistoryMessage::textHasLinks() const { return emptyText() ? false : _text.hasLinks(); } void HistoryMessage::setViewsCount(int32 count) { const auto views = Get(); if (!views || views->_views == count || (count >= 0 && views->_views > count)) { return; } const auto was = views->_viewsWidth; views->_views = count; views->_viewsText = (views->_views >= 0) ? FormatViewsCount(views->_views) : QString(); views->_viewsWidth = views->_viewsText.isEmpty() ? 0 : st::msgDateFont->width(views->_viewsText); if (was == views->_viewsWidth) { Auth().data().requestItemViewRepaint(this); } else { Auth().data().requestItemViewResize(this); } } void HistoryMessage::setRealId(MsgId newId) { HistoryItem::setRealId(newId); Auth().data().groups().refreshMessage(this); Auth().data().requestItemViewResize(this); } void HistoryMessage::dependencyItemRemoved(HistoryItem *dependency) { if (auto reply = Get()) { reply->itemRemoved(this, dependency); } } QString HistoryMessage::notificationHeader() const { return (!_history->peer->isUser() && !isPost()) ? from()->name : QString(); } std::unique_ptr HistoryMessage::createView( not_null delegate) { return delegate->elementCreate(this); } HistoryMessage::~HistoryMessage() { _media.reset(); if (auto reply = Get()) { reply->clearData(this); } }