diff --git a/Telegram/SourceFiles/api/api_bot.cpp b/Telegram/SourceFiles/api/api_bot.cpp index 8898ff605b..a6f1a3fa04 100644 --- a/Telegram/SourceFiles/api/api_bot.cpp +++ b/Telegram/SourceFiles/api/api_bot.cpp @@ -20,6 +20,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "chat_helpers/bot_command.h" #include "core/core_cloud_password.h" #include "core/click_handler_types.h" +#include "data/components/ephemeral_messages.h" #include "data/data_changes.h" #include "data/data_peer.h" #include "data/data_poll.h" @@ -58,7 +59,7 @@ void SendBotCallbackData( std::optional password, Fn done = nullptr, Fn handleError = nullptr) { - if (!item->isRegular()) { + if (!item->isRegular() && !item->isEphemeral()) { return; } const auto history = item->history(); @@ -91,15 +92,16 @@ void SendBotCallbackData( if (withPassword) { flags |= MTPmessages_GetBotCallbackAnswer::Flag::f_password; } + const auto ephemeralId = item->isEphemeral() + ? session->ephemeralMessages().lookupId(item) + : 0; + if (item->isEphemeral() && (!ephemeralId || isGame || withPassword)) { + return; + } const auto weak = base::make_weak(controller); const auto show = controller->uiShow(); - button->requestId = api->request(MTPmessages_GetBotCallbackAnswer( - MTP_flags(flags), - history->peer->input(), - MTP_int(item->id), - MTP_bytes(sendData), - password ? password->result : MTP_inputCheckPasswordEmpty() - )).done([=](const MTPmessages_BotCallbackAnswer &result) { + const auto handleDone = [=]( + const MTPmessages_BotCallbackAnswer &result) { const auto guard = gsl::finally([&] { if (done) { done(); @@ -149,7 +151,8 @@ void SendBotCallbackData( } else if (withPassword) { show->hideLayer(); } - }).fail([=](const MTP::Error &error) { + }; + const auto handleFail = [=](const MTP::Error &error) { const auto guard = gsl::finally([&] { if (handleError) { handleError(error.type()); @@ -164,7 +167,23 @@ void SendBotCallbackData( button->requestId = 0; owner->requestItemRepaint(item); } - }).send(); + }; + button->requestId = ephemeralId + ? api->request(MTPephemeral_GetCallbackAnswer( + MTP_flags(sendData.isEmpty() + ? MTPephemeral_GetCallbackAnswer::Flag(0) + : MTPephemeral_GetCallbackAnswer::Flag::f_data), + history->peer->input(), + MTP_int(ephemeralId), + MTP_bytes(sendData) + )).done(handleDone).fail(handleFail).send() + : api->request(MTPmessages_GetBotCallbackAnswer( + MTP_flags(flags), + history->peer->input(), + MTP_int(item->id), + MTP_bytes(sendData), + password ? password->result : MTP_inputCheckPasswordEmpty() + )).done(handleDone).fail(handleFail).send(); session->changes().messageUpdated( item, diff --git a/Telegram/SourceFiles/api/api_report.cpp b/Telegram/SourceFiles/api/api_report.cpp index 62f2458d6c..79981978a0 100644 --- a/Telegram/SourceFiles/api/api_report.cpp +++ b/Telegram/SourceFiles/api/api_report.cpp @@ -23,6 +23,32 @@ namespace Api { namespace { +[[nodiscard]] ReportResult ParseReportResult(const MTPReportResult &result) { + return result.match([&](const MTPDreportResultChooseOption &data) { + auto list = ReportResult::Options(); + list.reserve(data.voptions().v.size()); + for (const auto &tl : data.voptions().v) { + list.emplace_back(ReportResult::Option{ + .id = tl.data().voption().v, + .text = qs(tl.data().vtext()), + }); + } + return ReportResult{ + .options = std::move(list), + .title = qs(data.vtitle()), + }; + }, [&](const MTPDreportResultAddComment &data) -> ReportResult { + return { + .commentOption = ReportResult::CommentOption{ + .optional = data.is_optional(), + .id = data.voption().v, + } + }; + }, [&](const MTPDreportResultReported &data) -> ReportResult { + return { .successful = true }; + }); +} + MTPreportReason ReasonToTL(const Ui::ReportReason &reason) { using Reason = Ui::ReportReason; switch (reason) { @@ -62,11 +88,6 @@ auto CreateReportMessagesOrStoriesCallback( std::shared_ptr show, not_null peer) -> Fn)> { - using TLChoose = MTPDreportResultChooseOption; - using TLAddComment = MTPDreportResultAddComment; - using TLReported = MTPDreportResultReported; - using Result = ReportResult; - struct State final { #ifdef _DEBUG ~State() { @@ -79,7 +100,7 @@ auto CreateReportMessagesOrStoriesCallback( return [=]( Data::ReportInput reportInput, - Fn done) { + Fn done) { auto apiIds = QVector(); apiIds.reserve(reportInput.ids.size() + reportInput.stories.size()); for (const auto &id : reportInput.ids) { @@ -96,27 +117,7 @@ auto CreateReportMessagesOrStoriesCallback( return; } state->requestId = 0; - done(result.match([&](const TLChoose &data) { - const auto t = qs(data.vtitle()); - auto list = Result::Options(); - list.reserve(data.voptions().v.size()); - for (const auto &tl : data.voptions().v) { - list.emplace_back(Result::Option{ - .id = tl.data().voption().v, - .text = qs(tl.data().vtext()), - }); - } - return Result{ .options = std::move(list), .title = t }; - }, [&](const TLAddComment &data) -> Result { - return { - .commentOption = ReportResult::CommentOption{ - .optional = data.is_optional(), - .id = data.voption().v, - } - }; - }, [&](const TLReported &data) -> Result { - return { .successful = true }; - })); + done(ParseReportResult(result)); }; const auto fail = [=](const MTP::Error &error) { @@ -144,6 +145,44 @@ auto CreateReportMessagesOrStoriesCallback( }; } +auto CreateReportEphemeralMessageCallback( + std::shared_ptr show, + not_null peer, + int32 ephemeralId) +-> Fn)> { + struct State final { + mtpRequestId requestId = 0; + }; + const auto state = std::make_shared(); + + return [=]( + Data::ReportInput reportInput, + Fn done) { + const auto received = [=]( + const MTPReportResult &result, + mtpRequestId requestId) { + if (state->requestId != requestId) { + return; + } + state->requestId = 0; + done(ParseReportResult(result)); + }; + + const auto fail = [=](const MTP::Error &error) { + state->requestId = 0; + done({ .error = error.type() }); + }; + + state->requestId = peer->session().api().request( + MTPephemeral_ReportMessage( + peer->input(), + MTP_int(ephemeralId), + MTP_bytes(reportInput.optionId), + MTP_string(reportInput.comment)) + ).done(received).fail(fail).send(); + }; +} + ReactionReportCapabilities GetReactionReportCapabilities( not_null group, not_null participant) { diff --git a/Telegram/SourceFiles/api/api_report.h b/Telegram/SourceFiles/api/api_report.h index 0b35c408de..548f03c326 100644 --- a/Telegram/SourceFiles/api/api_report.h +++ b/Telegram/SourceFiles/api/api_report.h @@ -53,6 +53,12 @@ void SendPhotoReport( not_null peer) -> Fn)>; +[[nodiscard]] auto CreateReportEphemeralMessageCallback( + std::shared_ptr show, + not_null peer, + int32 ephemeralId) +-> Fn)>; + struct ReactionReportCapabilities final { bool canReport = false; bool canBan = false; diff --git a/Telegram/SourceFiles/boxes/report_messages_box.cpp b/Telegram/SourceFiles/boxes/report_messages_box.cpp index 08ac9e4f2f..32af95865e 100644 --- a/Telegram/SourceFiles/boxes/report_messages_box.cpp +++ b/Telegram/SourceFiles/boxes/report_messages_box.cpp @@ -9,8 +9,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "api/api_report.h" #include "core/application.h" +#include "data/components/ephemeral_messages.h" #include "data/data_peer.h" #include "data/data_photo.h" +#include "history/history.h" +#include "history/history_item.h" +#include "main/main_session.h" #include "lang/lang_keys.h" #include "ui/boxes/report_box_graphics.h" #include "ui/layers/generic_box.h" @@ -66,16 +70,12 @@ object_ptr ReportProfilePhotoBox( return ReportPhoto(peer, photo, nullptr); } -void ShowReportMessageBox( +void ShowReportFlowBox( std::shared_ptr show, not_null peer, - const std::vector &ids, - const std::vector &stories, + Fn)> report, + Data::ReportInput initialInput, const style::ReportBox *stOverride) { - const auto report = Api::CreateReportMessagesOrStoriesCallback( - show, - peer); - auto performRequest = [=]( const auto &repeatRequest, Data::ReportInput reportInput) -> void { @@ -203,5 +203,36 @@ void ShowReportMessageBox( } }); }; - performRequest(performRequest, { .ids = ids, .stories = stories }); + performRequest(performRequest, std::move(initialInput)); +} + +void ShowReportMessageBox( + std::shared_ptr show, + not_null peer, + const std::vector &ids, + const std::vector &stories, + const style::ReportBox *stOverride) { + ShowReportFlowBox( + show, + peer, + Api::CreateReportMessagesOrStoriesCallback(show, peer), + { .ids = ids, .stories = stories }, + stOverride); +} + +void ShowReportEphemeralBox( + std::shared_ptr show, + not_null item) { + const auto peer = item->history()->peer; + const auto ephemeralId = peer->session().ephemeralMessages().lookupId( + item); + if (!ephemeralId) { + return; + } + ShowReportFlowBox( + show, + peer, + Api::CreateReportEphemeralMessageCallback(show, peer, ephemeralId), + {}, + nullptr); } diff --git a/Telegram/SourceFiles/boxes/report_messages_box.h b/Telegram/SourceFiles/boxes/report_messages_box.h index 679e946175..d5af09376f 100644 --- a/Telegram/SourceFiles/boxes/report_messages_box.h +++ b/Telegram/SourceFiles/boxes/report_messages_box.h @@ -19,6 +19,7 @@ namespace style { struct ReportBox; } // namespace style +class HistoryItem; class PeerData; [[nodiscard]] object_ptr ReportProfilePhotoBox( @@ -31,3 +32,7 @@ void ShowReportMessageBox( const std::vector &ids, const std::vector &stories, const style::ReportBox *stOverride = nullptr); + +void ShowReportEphemeralBox( + std::shared_ptr show, + not_null item); diff --git a/Telegram/SourceFiles/history/history_inner_widget.cpp b/Telegram/SourceFiles/history/history_inner_widget.cpp index 24d716288a..0bb8e500cd 100644 --- a/Telegram/SourceFiles/history/history_inner_widget.cpp +++ b/Telegram/SourceFiles/history/history_inner_widget.cpp @@ -3204,7 +3204,7 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) { }; const auto addReplyAction = [&](HistoryItem *item) { - if (!item || !item->isRegular()) { + if (!item || (!item->isRegular() && !item->isEphemeral())) { return; } const auto canSendReply = CanSendReply(item); @@ -3375,6 +3375,10 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) { reportItem(itemId); }, &st::menuIconReport); } + HistoryView::AddEphemeralMessageActions( + _menu, + _controller->uiShow(), + item); } addSelectMessageAction(item); if (isUponSelected != -2 && blockSender) { @@ -3632,7 +3636,10 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) { _menu->addAction(tr::lng_context_clear_selection(tr::now), [=] { _widget->clearSelected(); }, &st::menuIconSelect); - } else if (item && ((isUponSelected != -2 && (canForward || canDelete)) || item->isRegular())) { + } else if (item + && ((isUponSelected != -2 && (canForward || canDelete)) + || item->isRegular() + || item->isEphemeral())) { if (isUponSelected != -2) { if (canForward) { _menu->addAction(tr::lng_context_forward_msg(tr::now), [=] { @@ -3670,6 +3677,10 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) { reportAsGroup(itemId); }, &st::menuIconReport); } + HistoryView::AddEphemeralMessageActions( + _menu, + _controller->uiShow(), + item); } addSelectMessageAction(partItemOrLeader); if (isUponSelected != -2 && canBlockSender) { diff --git a/Telegram/SourceFiles/history/history_item.cpp b/Telegram/SourceFiles/history/history_item.cpp index 7c3a00350b..68bd8d0e3e 100644 --- a/Telegram/SourceFiles/history/history_item.cpp +++ b/Telegram/SourceFiles/history/history_item.cpp @@ -3066,6 +3066,8 @@ bool HistoryItem::forbidsSaving() const { bool HistoryItem::canDelete() const { if (isSponsored()) { return false; + } else if (isEphemeral()) { + return false; } else if (IsStoryMsgId(id)) { return false; } else if (isService() && !isRegular()) { diff --git a/Telegram/SourceFiles/history/view/history_view_context_menu.cpp b/Telegram/SourceFiles/history/view/history_view_context_menu.cpp index 41eb7de685..720880088a 100644 --- a/Telegram/SourceFiles/history/view/history_view_context_menu.cpp +++ b/Telegram/SourceFiles/history/view/history_view_context_menu.cpp @@ -66,6 +66,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "boxes/delete_messages_box.h" #include "boxes/moderate_messages_box.h" #include "boxes/report_messages_box.h" +#include "data/components/ephemeral_messages.h" +#include "styles/style_layers.h" #include "boxes/sticker_set_box.h" #include "boxes/stickers_box.h" #include "boxes/translate_box.h" @@ -629,7 +631,7 @@ bool AddReplyToMessageAction( const auto topic = item ? item->topic() : nullptr; const auto peer = item ? item->history()->peer.get() : nullptr; if (!item - || !item->isRegular() + || (!item->isRegular() && !item->isEphemeral()) || (context != Context::History && context != Context::Replies && context != Context::Monoforum)) { @@ -1070,6 +1072,12 @@ void AddMessageActions( AddDeleteAction(menu, request, list); AddDownloadFilesAction(menu, request, list); AddReportAction(menu, request, list); + if (request.item && request.selectedItems.empty()) { + AddEphemeralMessageActions( + menu, + list->controller()->uiShow(), + request.item); + } AddSelectionAction(menu, request, list); AddRescheduleAction(menu, request, list); } @@ -2604,6 +2612,49 @@ void AddSelectRestrictionAction( menu->addAction(std::move(button)); } +void AddEphemeralMessageActions( + not_null menu, + std::shared_ptr show, + not_null item) { + if (!item->isEphemeral()) { + return; + } + const auto owner = &item->history()->owner(); + const auto session = &item->history()->session(); + const auto itemId = item->fullId(); + if (!item->out()) { + menu->addAction(tr::lng_context_report_msg(tr::now), [=] { + if (const auto item = owner->message(itemId)) { + ShowReportEphemeralBox(show, item); + } + }, &st::menuIconReport); + } + menu->addAction(tr::lng_context_delete_msg(tr::now), [=] { + show->show(Ui::MakeConfirmBox({ + .text = tr::lng_selected_delete_sure_this(), + .confirmed = [=](Fn &&close) { + close(); + if (const auto item = owner->message(itemId)) { + session->ephemeralMessages().deleteMessage(item); + } + }, + .confirmText = tr::lng_box_delete(), + .confirmStyle = &st::attentionBoxButton, + })); + }, &st::menuIconDelete); + if (!menu->empty()) { + menu->addSeparator(); + } + auto label = base::make_unique_q( + menu->menu(), + menu->st().menu, + st::historyHasCustomEmoji, + st::historyHasCustomEmojiPosition, + tr::lng_ephemeral_about(tr::now, tr::rich)); + label->setAttribute(Qt::WA_TransparentForMouseEvents); + menu->addAction(std::move(label)); +} + TextWithEntities TransribedText(not_null item) { const auto media = item->media(); const auto document = media ? media->document() : nullptr; diff --git a/Telegram/SourceFiles/history/view/history_view_context_menu.h b/Telegram/SourceFiles/history/view/history_view_context_menu.h index d1b5857185..aca0483bae 100644 --- a/Telegram/SourceFiles/history/view/history_view_context_menu.h +++ b/Telegram/SourceFiles/history/view/history_view_context_menu.h @@ -22,6 +22,7 @@ class SessionShow; namespace Ui { class PopupMenu; +class Show; enum class ReportReason; } // namespace Ui @@ -157,6 +158,10 @@ void AddSelectRestrictionAction( not_null menu, not_null item, bool addIcon); +void AddEphemeralMessageActions( + not_null menu, + std::shared_ptr show, + not_null item); [[nodiscard]] TextWithEntities TransribedText(not_null item); diff --git a/Telegram/SourceFiles/history/view/history_view_list_widget.cpp b/Telegram/SourceFiles/history/view/history_view_list_widget.cpp index 7f95f6bdf1..ed50e071fa 100644 --- a/Telegram/SourceFiles/history/view/history_view_list_widget.cpp +++ b/Telegram/SourceFiles/history/view/history_view_list_widget.cpp @@ -3225,7 +3225,9 @@ void ListWidget::mouseDoubleClickEvent(QMouseEvent *e) { || _mouseCursorState == CursorState::Date) && _selected.empty() && _overElement - && _overElement->data()->isRegular()) { + && (_overElement->data()->isRegular() + || (_overElement->data()->isEphemeral() + && !_overElement->data()->out()))) { mouseActionCancel(); switch (CurrentQuickAction()) { case DoubleClickQuickAction::Reply: { diff --git a/Telegram/SourceFiles/ui/effects/thanos_effect_controller.cpp b/Telegram/SourceFiles/ui/effects/thanos_effect_controller.cpp index 015304b260..8f0ef42d41 100644 --- a/Telegram/SourceFiles/ui/effects/thanos_effect_controller.cpp +++ b/Telegram/SourceFiles/ui/effects/thanos_effect_controller.cpp @@ -99,7 +99,7 @@ void ThanosEffectController::captureOnRemoval( const auto top = _delegate.itemTop(view); const auto height = view->height(); - if (!item->isRegular() + if ((!item->isRegular() && !item->isEphemeral()) || item->isService() || top < 0 || height <= 0 @@ -126,7 +126,7 @@ bool ThanosEffectController::captureView( int viewHeight, int viewTop) { const auto item = view->data(); - if (!item->isRegular() || item->isService()) { + if ((!item->isRegular() && !item->isEphemeral()) || item->isService()) { return false; } if (viewTop < 0) {