diff --git a/Telegram/SourceFiles/api/api_sending.cpp b/Telegram/SourceFiles/api/api_sending.cpp index 92166187b0..14f4fccc1e 100644 --- a/Telegram/SourceFiles/api/api_sending.cpp +++ b/Telegram/SourceFiles/api/api_sending.cpp @@ -19,6 +19,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_file_origin.h" #include "data/data_histories.h" #include "data/data_changes.h" +#include "data/components/ephemeral_messages.h" #include "data/stickers/data_stickers.h" #include "history/history.h" #include "history/history_item.h" @@ -183,6 +184,12 @@ void SendExistingMedia( flags |= MessageFlag::HasReplyInfo; sendFlags |= MTPmessages_SendMedia::Flag::f_reply_to; } + if (!action.options.scheduled + && !action.options.shortcutId + && session->ephemeralMessages().isEphemeralBotReply( + action.replyTo.messageId)) { + flags |= MessageFlag::Ephemeral; + } const auto silentPost = ShouldSendSilent(peer, action.options); InnerFillMessagePostFlags(action.options, peer, flags); if (silentPost) { @@ -236,7 +243,7 @@ void SendExistingMedia( session->data().registerMessageRandomId(randomId, newId); - history->addNewLocalMessage({ + const auto item = history->addNewLocalMessage({ .id = newId.msg, .flags = flags, .from = NewMessageFromId(action), @@ -251,6 +258,11 @@ void SendExistingMedia( .mediaSpoiler = action.options.mediaSpoiler, }, media, caption); + if (session->ephemeralMessages().sendMedia(item, inputMedia())) { + api->finishForwarding(action); + return; + } + const auto performRequest = [=](const auto &repeatRequest) -> void { auto &histories = history->owner().histories(); const auto session = &history->session(); @@ -587,6 +599,14 @@ void SendConfirmedFile( if (file->to.replyTo) { flags |= MessageFlag::HasReplyInfo; } + if (!isEditing + && !groupId + && !file->to.options.scheduled + && !file->to.options.shortcutId + && session->ephemeralMessages().isEphemeralBotReply( + file->to.replyTo.messageId)) { + flags |= MessageFlag::Ephemeral; + } FillMessagePostFlags(action, peer, flags); if (file->to.options.scheduled) { flags |= MessageFlag::IsOrWasScheduled; diff --git a/Telegram/SourceFiles/apiwrap.cpp b/Telegram/SourceFiles/apiwrap.cpp index ebd0eda774..e7eb440cc7 100644 --- a/Telegram/SourceFiles/apiwrap.cpp +++ b/Telegram/SourceFiles/apiwrap.cpp @@ -4189,7 +4189,6 @@ void ApiWrap::sendFiles( SendMediaType type, std::shared_ptr album, SendAction action) { - StripEphemeralReply(_session, action.replyTo); const auto to = FileLoadTaskOptions(action); if (album) { album->options = to.options; @@ -4577,7 +4576,9 @@ void ApiWrap::sendMessage( ? replyTo->topicRootId() : Data::ForumTopic::kGeneralId; const auto topic = peer->forumTopicFor(topicRootId); - if (!(topic ? Data::CanSendTexts(topic) : Data::CanSendTexts(peer))) { + const auto ephemeral = _session->ephemeralMessages().wouldSend(message); + if (!ephemeral + && !(topic ? Data::CanSendTexts(topic) : Data::CanSendTexts(peer))) { return; } else if (_session->ephemeralMessages().trySend(message)) { if (clearCloudDraft) { @@ -5096,6 +5097,13 @@ void ApiWrap::sendMediaWithRandomId( const auto replyTo = item->replyTo(); const auto peer = history->peer; + if (_session->ephemeralMessages().sendMedia(item, media)) { + if (done) { + done(true); + } + return; + } + auto caption = item->originalText(); TextUtilities::Trim(caption); auto sentEntities = Api::EntitiesToMTP( @@ -5317,7 +5325,29 @@ void ApiWrap::sendAlbumIfReady(not_null album) { if (!sample) { _sendingAlbums.remove(groupId); return; - } else if (album->options.price > 0) { + } + const auto replyTo = sample->replyTo(); + if (const auto target = _session->data().message(replyTo.messageId) + ; target && target->isEphemeral()) { + album->sent = true; + auto parts = std::vector< + std::pair, MTPInputMedia>>(); + parts.reserve(album->items.size()); + for (const auto &part : album->items) { + const auto partItem = part.media + ? _session->data().message(part.msgId) + : nullptr; + if (partItem) { + parts.emplace_back(partItem, part.media->data().vmedia()); + } + } + _sendingAlbums.remove(groupId); + for (const auto &[partItem, media] : parts) { + (void)_session->ephemeralMessages().sendMedia(partItem, media); + } + return; + } + if (album->options.price > 0) { sendMultiPaidMedia(sample, album); return; } else if (medias.size() < 2) { @@ -5332,7 +5362,6 @@ void ApiWrap::sendAlbumIfReady(not_null album) { return; } const auto history = sample->history(); - const auto replyTo = sample->replyTo(); const auto sendAs = album->options.sendAs; const auto starsPaid = std::min( history->peer->starsPerMessageChecked() * int(medias.size()), diff --git a/Telegram/SourceFiles/data/components/ephemeral_messages.cpp b/Telegram/SourceFiles/data/components/ephemeral_messages.cpp index 9c96a5bb30..ca25935b4d 100644 --- a/Telegram/SourceFiles/data/components/ephemeral_messages.cpp +++ b/Telegram/SourceFiles/data/components/ephemeral_messages.cpp @@ -226,6 +226,24 @@ HistoryItem *EphemeralMessages::applyNew(const MTPDephemeralMessage &data) { replyTo.messageId = { history->peer->id, hinted }; } } + if (_convertLocalMediaTarget && data.is_out() && data.vmedia()) { + const auto localId = base::take(_convertLocalMediaTarget); + if (const auto local = _session->data().message(localId)) { + if (const auto media = local->media()) { + media->updateSentMedia(*data.vmedia()); + } + if (local->isEphemeral()) { + local->markEphemeralSent(); + _data[history].push_back({ + .ephemeralId = ephemeralId, + .receiverId = UserId(data.vreceiver_id()), + .item = local, + }); + _session->data().requestItemResize(local); + return local; + } + } + } const auto item = history->addNewLocalMessage( { .id = _session->data().nextLocalMessageId(), @@ -315,6 +333,11 @@ bool EphemeralMessages::wouldSend(const Api::MessageToSend &message) const { != nullptr; } +bool EphemeralMessages::isEphemeralBotReply(FullMsgId replyToId) const { + const auto target = _session->data().message(replyToId); + return target && target->isEphemeral() && !target->out(); +} + FullMsgId EphemeralMessages::realReplyId( const Api::MessageToSend &message) const { const auto &replyTo = message.action.replyTo; @@ -412,7 +435,60 @@ void EphemeralMessages::send( TextWithEntities text, int32 replyToEphemeralId, MsgId topicRootId) { + request( + history, + bot, + std::move(text), + MTPInputMedia(), + false, + replyToEphemeralId, + topicRootId); +} + +bool EphemeralMessages::sendMedia( + not_null item, + const MTPInputMedia &media) { + const auto target = _session->data().message(item->replyTo().messageId); + if (!target || !target->isEphemeral()) { + return false; + } + if (!target->out()) { + const auto entry = findByItem(target); + const auto bot = entry ? botForSending(*entry) : nullptr; + if (bot) { + request( + item->history(), + bot, + item->originalText(), + media, + true, + entry->ephemeralId, + MsgId(0), + item->fullId()); + return true; + } + } + item->destroy(); + return true; +} + +void EphemeralMessages::request( + not_null history, + not_null bot, + TextWithEntities text, + const MTPInputMedia &media, + bool hasMedia, + int32 replyToEphemeralId, + MsgId topicRootId, + FullMsgId destroyOnResult) { const auto session = _session; + const auto destroyLocal = [=] { + if (destroyOnResult) { + if (const auto local = session->data().message(destroyOnResult)) { + local->destroy(); + } + } + }; const auto entities = Api::EntitiesToMTP( session, text.entities, @@ -433,6 +509,7 @@ void EphemeralMessages::send( using Flag = MTPephemeral_SendMessage::Flag; const auto flags = Flag(0) | (entities.v.isEmpty() ? Flag(0) : Flag::f_entities) + | (hasMedia ? Flag::f_media : Flag(0)) | (hasReplyTo ? Flag::f_reply_to : Flag(0)); session->api().request(MTPephemeral_SendMessage( MTP_flags(flags), @@ -441,15 +518,23 @@ void EphemeralMessages::send( MTPlong(), // query_id MTP_string(text.text), entities, - MTPInputMedia(), + media, MTPReplyMarkup(), MTPInputRichMessage(), MTP_long(base::RandomValue()), replyTo )).done([=](const MTPUpdates &result) { + _convertLocalMediaTarget = destroyOnResult; session->api().applyUpdates(result); + if (destroyOnResult) { + const auto local = session->data().message(destroyOnResult); + if (local && !findByItem(local)) { + local->destroy(); + } + } }).fail([=](const MTP::Error &error) { LOG(("API Error: send ephemeral message - %1").arg(error.type())); + destroyLocal(); }).send(); } diff --git a/Telegram/SourceFiles/data/components/ephemeral_messages.h b/Telegram/SourceFiles/data/components/ephemeral_messages.h index 598762eea5..5a03e7fe62 100644 --- a/Telegram/SourceFiles/data/components/ephemeral_messages.h +++ b/Telegram/SourceFiles/data/components/ephemeral_messages.h @@ -43,6 +43,7 @@ public: not_null item) const; [[nodiscard]] bool wouldSend(const Api::MessageToSend &message) const; + [[nodiscard]] bool isEphemeralBotReply(FullMsgId replyToId) const; [[nodiscard]] bool trySend(const Api::MessageToSend &message); void send( not_null history, @@ -50,6 +51,9 @@ public: TextWithEntities text, int32 replyToEphemeralId = 0, MsgId topicRootId = 0); + [[nodiscard]] bool sendMedia( + not_null item, + const MTPInputMedia &media); void noteCallbackTopic( not_null history, PeerId botId, @@ -71,6 +75,15 @@ private: const QString &text) const; [[nodiscard]] FullMsgId realReplyId( const Api::MessageToSend &message) const; + void request( + not_null history, + not_null bot, + TextWithEntities text, + const MTPInputMedia &media, + bool hasMedia, + int32 replyToEphemeralId, + MsgId topicRootId, + FullMsgId destroyOnResult = {}); [[nodiscard]] bool replyTargetMissing( const MTPDephemeralMessage &data) const; void drainPending(bool force = false); @@ -89,6 +102,7 @@ private: base::Timer _pendingTimer; base::flat_map, List> _data; std::vector _pending; + FullMsgId _convertLocalMediaTarget; base::flat_map< not_null, base::flat_map> _callbackTopicHints; diff --git a/Telegram/SourceFiles/history/history_item.cpp b/Telegram/SourceFiles/history/history_item.cpp index 68bd8d0e3e..703e0b58c9 100644 --- a/Telegram/SourceFiles/history/history_item.cpp +++ b/Telegram/SourceFiles/history/history_item.cpp @@ -2918,6 +2918,16 @@ QString HistoryItem::notificationHeader() const { return QString(); } +void HistoryItem::markEphemeralSent() { + if (!(_flags & MessageFlag::BeingSent)) { + return; + } + _flags &= ~MessageFlag::BeingSent; + _history->owner().notifyItemDataChange(this); + _history->owner().requestItemResize(this); + _history->owner().requestItemRepaint(this); +} + void HistoryItem::markTextAppearingStarted() { _flags |= MessageFlag::TextAppearingStarted; } diff --git a/Telegram/SourceFiles/history/history_item.h b/Telegram/SourceFiles/history/history_item.h index be35439676..24e2704e34 100644 --- a/Telegram/SourceFiles/history/history_item.h +++ b/Telegram/SourceFiles/history/history_item.h @@ -455,6 +455,7 @@ public: bool isForumPost); void setPostAuthor(const QString &author); void setRealId(MsgId newId); + void markEphemeralSent(); void markTextAppearingStarted(); void incrementReplyToTopCounter(); void applyEffectWatchedOnUnreadKnown(); diff --git a/Telegram/SourceFiles/history/history_item_helpers.cpp b/Telegram/SourceFiles/history/history_item_helpers.cpp index 2b172f5990..6911a4ccc6 100644 --- a/Telegram/SourceFiles/history/history_item_helpers.cpp +++ b/Telegram/SourceFiles/history/history_item_helpers.cpp @@ -127,7 +127,7 @@ Data::SendError GetErrorForSending( } const auto hasText = request.richMessage || (request.text && !request.text->empty()); - if (hasText) { + if (hasText && !request.ignoreRestrictions) { const auto error = Data::RestrictionError( peer, ChatRestriction::SendOther); @@ -137,7 +137,7 @@ Data::SendError GetErrorForSending( return tr::lng_forward_cant(tr::now); } } - if (peer->slowmodeApplied()) { + if (peer->slowmodeApplied() && !request.ignoreRestrictions) { const auto count = request.messagesCount ? request.messagesCount : ComputeSendingMessagesCount(thread->owningHistory(), request); @@ -171,7 +171,7 @@ Data::SendError GetErrorForSending( } } if (const auto left = peer->slowmodeSecondsLeft()) { - if (!request.ignoreSlowmodeCountdown) { + if (!request.ignoreSlowmodeCountdown && !request.ignoreRestrictions) { return tr::lng_slowmode_enabled( tr::now, lt_left, diff --git a/Telegram/SourceFiles/history/history_item_helpers.h b/Telegram/SourceFiles/history/history_item_helpers.h index bf0aaa18fd..4f2495c882 100644 --- a/Telegram/SourceFiles/history/history_item_helpers.h +++ b/Telegram/SourceFiles/history/history_item_helpers.h @@ -144,6 +144,7 @@ struct SendingErrorRequest { int messagesCount = 0; bool ignoreSlowmodeCountdown = false; bool richMessage = false; + bool ignoreRestrictions = false; }; [[nodiscard]] int ComputeSendingMessagesCount( not_null history, diff --git a/Telegram/SourceFiles/history/history_widget.cpp b/Telegram/SourceFiles/history/history_widget.cpp index a1503d1897..26ae3a7172 100644 --- a/Telegram/SourceFiles/history/history_widget.cpp +++ b/Telegram/SourceFiles/history/history_widget.cpp @@ -5429,11 +5429,6 @@ Api::SendAction HistoryWidget::prepareSendAction( void HistoryWidget::sendVoice(const VoiceToSend &data) { if (!canWriteMessage() || data.bytes.isEmpty() || !_history) { return; - } else if (ShowEphemeralReplyTextOnlyError( - controller()->uiShow(), - &session(), - replyTo().messageId)) { - return; } const auto withPaymentApproved = [=](int approved) { @@ -5468,9 +5463,18 @@ void HistoryWidget::send(Api::SendOptions options) { } else if (const auto page = shownRichMessage()) { sendRichDraft(page, options); return; - } else if (!options.scheduled && showSlowmodeError()) { - return; - } else if (_voiceRecordBar->isListenState()) { + } + if (!options.scheduled) { + auto action = Api::SendAction(_history, options); + action.replyTo = replyTo(); + auto message = Api::MessageToSend(std::move(action)); + message.textWithTags = _field->getTextWithAppliedMarkdown(); + if (!session().ephemeralMessages().wouldSend(message) + && showSlowmodeError()) { + return; + } + } + if (_voiceRecordBar->isListenState()) { _voiceRecordBar->requestToSendWithOptions(options); return; } @@ -5607,8 +5611,8 @@ void HistoryWidget::sendTextWithTags( if (useWebPageDraft && _preview) { message.webPage = _preview->draft(); } - if (options.scheduled - && session().ephemeralMessages().wouldSend(message)) { + const auto ephemeral = session().ephemeralMessages().wouldSend(message); + if (options.scheduled && ephemeral) { controller()->showToast(tr::lng_ephemeral_cant_schedule(tr::now)); return; } @@ -5623,7 +5627,8 @@ void HistoryWidget::sendTextWithTags( message.textWithTags, ignoreSlowmodeCountdown, withPaymentApproved, - message.action.options)) { + message.action.options, + ephemeral)) { return; } @@ -6064,11 +6069,15 @@ void HistoryWidget::chooseAttach( if (!_peer || !_canSendMessages) { return; - } else if (const auto error = Data::AnyFileRestrictionError(_peer)) { - Data::ShowSendErrorToast(controller(), _peer, error); - return; - } else if (showSlowmodeError()) { - return; + } + if (!session().ephemeralMessages().isEphemeralBotReply( + replyTo().messageId)) { + if (const auto error = Data::AnyFileRestrictionError(_peer)) { + Data::ShowSendErrorToast(controller(), _peer, error); + return; + } else if (showSlowmodeError()) { + return; + } } const auto filter = (overrideSendImagesAsPhotos == true) @@ -7377,7 +7386,8 @@ bool HistoryWidget::showSendMessageError( const TextWithTags &textWithTags, bool ignoreSlowmodeCountdown, Fn withPaymentApproved, - Api::SendOptions options) { + Api::SendOptions options, + bool ignoreRestrictions) { if (!_canSendMessages) { return false; } @@ -7387,6 +7397,7 @@ bool HistoryWidget::showSendMessageError( .forward = &_forwardPanel->items(), .text = &textWithTags, .ignoreSlowmodeCountdown = ignoreSlowmodeCountdown, + .ignoreRestrictions = ignoreRestrictions, }; request.messagesCount = ComputeSendingMessagesCount(_history, request); const auto error = GetErrorForSending(_peer, request); @@ -7451,13 +7462,6 @@ bool HistoryWidget::confirmSendingFiles( bool HistoryWidget::confirmSendingFiles( Ui::PreparedList &&list, const QString &insertTextOnCancel) { - if (!_editMsgId - && ShowEphemeralReplyTextOnlyError( - controller()->uiShow(), - &session(), - replyTo().messageId)) { - return false; - } if (_editMsgId) { if (_canReplaceMedia || _canAddMedia) { EditCaptionBox::StartMediaReplace( @@ -9541,21 +9545,17 @@ bool HistoryWidget::sendExistingDocument( not_null document, Api::MessageToSend messageToSend, std::optional localId) { - const auto error = _peer + const auto ephemeralReply = session().ephemeralMessages() + .isEphemeralBotReply(messageToSend.action.replyTo.messageId); + const auto error = (_peer && !ephemeralReply) ? Data::RestrictionError(_peer, ChatRestriction::SendStickers) : Data::SendError(); - if (ShowEphemeralReplyTextOnlyError( - controller()->uiShow(), - &session(), - messageToSend.action.replyTo.messageId)) { - return false; - } if (error) { Data::ShowSendErrorToast(controller(), _peer, error); return false; } else if (!_peer || !_canSendMessages - || showSlowmodeError() + || (!ephemeralReply && showSlowmodeError()) || ShowSendPremiumError(controller(), document)) { return false; } @@ -9594,21 +9594,17 @@ bool HistoryWidget::sendExistingDocument( bool HistoryWidget::sendExistingPhoto( not_null photo, Api::SendOptions options) { - const auto error = _peer + const auto ephemeralReply = session().ephemeralMessages() + .isEphemeralBotReply(replyTo().messageId); + const auto error = (_peer && !ephemeralReply) ? Data::RestrictionError(_peer, ChatRestriction::SendPhotos) : Data::SendError(); - if (ShowEphemeralReplyTextOnlyError( - controller()->uiShow(), - &session(), - replyTo().messageId)) { - return false; - } if (error) { Data::ShowSendErrorToast(controller(), _peer, error); return false; } else if (!_peer || !_canSendMessages) { return false; - } else if (showSlowmodeError()) { + } else if (!ephemeralReply && showSlowmodeError()) { return false; } const auto action = prepareSendAction(options); diff --git a/Telegram/SourceFiles/history/history_widget.h b/Telegram/SourceFiles/history/history_widget.h index 5e2ceae599..594245dc66 100644 --- a/Telegram/SourceFiles/history/history_widget.h +++ b/Telegram/SourceFiles/history/history_widget.h @@ -537,7 +537,8 @@ private: const TextWithTags &textWithTags, bool ignoreSlowmodeCountdown, Fn withPaymentApproved = nullptr, - Api::SendOptions options = {}); + Api::SendOptions options = {}, + bool ignoreRestrictions = false); bool showSendRichDraftError( bool ignoreSlowmodeCountdown, Fn withPaymentApproved = nullptr, diff --git a/Telegram/SourceFiles/history/view/history_view_chat_section.cpp b/Telegram/SourceFiles/history/view/history_view_chat_section.cpp index 84f3cd470a..beae1e5b30 100644 --- a/Telegram/SourceFiles/history/view/history_view_chat_section.cpp +++ b/Telegram/SourceFiles/history/view/history_view_chat_section.cpp @@ -1221,11 +1221,6 @@ bool ChatWidget::confirmSendingFiles( return true; } else if (showSendingFilesError(list)) { return false; - } else if (ShowEphemeralReplyTextOnlyError( - controller()->uiShow(), - &session(), - _composeControls->replyingToMessage().messageId)) { - return false; } auto box = Box( @@ -1414,12 +1409,6 @@ void ChatWidget::send() { } void ChatWidget::sendVoice(const ComposeControls::VoiceToSend &data) { - if (ShowEphemeralReplyTextOnlyError( - controller()->uiShow(), - &session(), - replyTo().messageId)) { - return; - } const auto withPaymentApproved = [=](int approved) { auto copy = data; copy.options.starsApproved = approved; @@ -1451,8 +1440,13 @@ void ChatWidget::send(Api::SendOptions options) { sendRichDraft(page, options); return; } - if (!options.scheduled && showSlowmodeError()) { - return; + if (!options.scheduled) { + auto message = Api::MessageToSend(prepareSendAction(options)); + message.textWithTags = _composeControls->getTextWithAppliedMarkdown(); + if (!session().ephemeralMessages().wouldSend(message) + && showSlowmodeError()) { + return; + } } sendTextWithTags( @@ -1592,11 +1586,13 @@ void ChatWidget::sendTextWithTags( return; } + const auto ephemeral = session().ephemeralMessages().wouldSend(message); auto request = SendingErrorRequest{ .topicRootId = _topic ? _topic->rootId() : MsgId(0), .forward = &_composeControls->forwardItems(), .text = &message.textWithTags, .ignoreSlowmodeCountdown = (options.scheduled != 0), + .ignoreRestrictions = ephemeral, }; request.messagesCount = ComputeSendingMessagesCount(_history, request); const auto error = GetErrorForSending(_peer, request); @@ -1898,11 +1894,6 @@ bool ChatWidget::sendExistingDocument( } else if (showSlowmodeError() || ShowSendPremiumError(controller(), document)) { return false; - } else if (ShowEphemeralReplyTextOnlyError( - controller()->uiShow(), - &session(), - messageToSend.action.replyTo.messageId)) { - return false; } const auto withPaymentApproved = [=](int approved) { auto copy = messageToSend; @@ -1942,11 +1933,6 @@ bool ChatWidget::sendExistingPhoto( return false; } else if (showSlowmodeError()) { return false; - } else if (ShowEphemeralReplyTextOnlyError( - controller()->uiShow(), - &session(), - replyTo().messageId)) { - return false; } const auto withPaymentApproved = [=](int approved) {