2
0
mirror of https://github.com/telegramdesktop/tdesktop synced 2025-08-31 06:26:18 +00:00

Start suggesting changes to messages by editing.

This commit is contained in:
John Preston
2025-06-20 21:31:54 +04:00
parent 498116c3f6
commit dc19f2e76c
10 changed files with 496 additions and 138 deletions

View File

@@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "apiwrap.h"
#include "api/api_media.h"
#include "api/api_text_entities.h"
#include "base/random.h"
#include "ui/boxes/confirm_box.h"
#include "data/business/data_shortcut_messages.h"
#include "data/components/scheduled_messages.h"
@@ -20,6 +21,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_web_page.h"
#include "history/view/controls/history_view_compose_media_edit_manager.h"
#include "history/history.h"
#include "history/history_item_components.h"
#include "lang/lang_keys.h"
#include "main/main_session.h"
#include "mtproto/mtproto_response.h"
@@ -46,6 +48,230 @@ template <typename T>
constexpr auto ErrorWithoutId
= is_callable_plain_v<T, QString>;
template <typename DoneCallback, typename FailCallback>
mtpRequestId SuggestMessage(
not_null<HistoryItem*> item,
const TextWithEntities &textWithEntities,
Data::WebPageDraft webpage,
SendOptions options,
DoneCallback &&done,
FailCallback &&fail) {
Expects(options.suggest.exists);
Expects(!options.scheduled);
const auto session = &item->history()->session();
const auto api = &session->api();
const auto text = textWithEntities.text;
const auto sentEntities = EntitiesToMTP(
session,
textWithEntities.entities,
ConvertOption::SkipLocal);
const auto emptyFlag = MTPmessages_SendMessage::Flag(0);
auto replyTo = FullReplyTo{
.messageId = item->fullId(),
.monoforumPeerId = (item->history()->amMonoforumAdmin()
? item->sublistPeerId()
: PeerId()),
};
const auto flags = emptyFlag
| MTPmessages_SendMessage::Flag::f_reply_to
| MTPmessages_SendMessage::Flag::f_suggested_post
| (webpage.removed
? MTPmessages_SendMessage::Flag::f_no_webpage
: emptyFlag)
| (((!webpage.removed && !webpage.url.isEmpty() && webpage.invert)
|| options.invertCaption)
? MTPmessages_SendMessage::Flag::f_invert_media
: emptyFlag)
| (!sentEntities.v.isEmpty()
? MTPmessages_SendMessage::Flag::f_entities
: emptyFlag)
| (options.starsApproved
? MTPmessages_SendMessage::Flag::f_allow_paid_stars
: emptyFlag);
const auto randomId = base::RandomValue<uint64>();
return api->request(MTPmessages_SendMessage(
MTP_flags(flags),
item->history()->peer->input,
ReplyToForMTP(item->history(), replyTo),
MTP_string(text),
MTP_long(randomId),
MTPReplyMarkup(),
sentEntities,
MTPint(), // schedule_date
MTPInputPeer(), // send_as
MTPInputQuickReplyShortcut(), // quick_reply_shortcut
MTPlong(), // effect
MTP_long(options.starsApproved),
Api::SuggestToMTP(options.suggest)
)).done([=](
const MTPUpdates &result,
[[maybe_unused]] mtpRequestId requestId) {
const auto apply = [=] { api->applyUpdates(result); };
if constexpr (WithId<DoneCallback>) {
done(apply, requestId);
} else if constexpr (WithoutId<DoneCallback>) {
done(apply);
} else if constexpr (WithoutCallback<DoneCallback>) {
done();
apply();
} else {
t_bad_callback(done);
}
}).fail([=](const MTP::Error &error, mtpRequestId requestId) {
if constexpr (ErrorWithId<FailCallback>) {
fail(error.type(), requestId);
} else if constexpr (ErrorWithoutId<FailCallback>) {
fail(error.type());
} else if constexpr (WithoutCallback<FailCallback>) {
fail();
} else {
t_bad_callback(fail);
}
}).send();
}
template <typename DoneCallback, typename FailCallback>
mtpRequestId SuggestMedia(
not_null<HistoryItem*> item,
const TextWithEntities &textWithEntities,
Data::WebPageDraft webpage,
SendOptions options,
DoneCallback &&done,
FailCallback &&fail,
std::optional<MTPInputMedia> inputMedia) {
Expects(options.suggest.exists);
Expects(!options.scheduled);
const auto session = &item->history()->session();
const auto api = &session->api();
const auto text = textWithEntities.text;
const auto sentEntities = EntitiesToMTP(
session,
textWithEntities.entities,
ConvertOption::SkipLocal);
const auto updateRecentStickers = inputMedia
? Api::HasAttachedStickers(*inputMedia)
: false;
const auto emptyFlag = MTPmessages_SendMedia::Flag(0);
auto replyTo = FullReplyTo{
.messageId = item->fullId(),
.monoforumPeerId = (item->history()->amMonoforumAdmin()
? item->sublistPeerId()
: PeerId()),
};
const auto flags = emptyFlag
| MTPmessages_SendMedia::Flag::f_reply_to
| MTPmessages_SendMedia::Flag::f_suggested_post
| (((!webpage.removed && !webpage.url.isEmpty() && webpage.invert)
|| options.invertCaption)
? MTPmessages_SendMedia::Flag::f_invert_media
: emptyFlag)
| (!sentEntities.v.isEmpty()
? MTPmessages_SendMedia::Flag::f_entities
: emptyFlag)
| (options.starsApproved
? MTPmessages_SendMedia::Flag::f_allow_paid_stars
: emptyFlag);
const auto randomId = base::RandomValue<uint64>();
return api->request(MTPmessages_SendMedia(
MTP_flags(flags),
item->history()->peer->input,
ReplyToForMTP(item->history(), replyTo),
inputMedia.value_or(Data::WebPageForMTP(webpage, text.isEmpty())),
MTP_string(text),
MTP_long(randomId),
MTPReplyMarkup(),
sentEntities,
MTPint(), // schedule_date
MTPInputPeer(), // send_as
MTPInputQuickReplyShortcut(), // quick_reply_shortcut
MTPlong(), // effect
MTP_long(options.starsApproved),
Api::SuggestToMTP(options.suggest)
)).done([=](
const MTPUpdates &result,
[[maybe_unused]] mtpRequestId requestId) {
const auto apply = [=] { api->applyUpdates(result); };
if constexpr (WithId<DoneCallback>) {
done(apply, requestId);
} else if constexpr (WithoutId<DoneCallback>) {
done(apply);
} else if constexpr (WithoutCallback<DoneCallback>) {
done();
apply();
} else {
t_bad_callback(done);
}
if (updateRecentStickers) {
api->requestSpecialStickersForce(false, false, true);
}
}).fail([=](const MTP::Error &error, mtpRequestId requestId) {
if constexpr (ErrorWithId<FailCallback>) {
fail(error.type(), requestId);
} else if constexpr (ErrorWithoutId<FailCallback>) {
fail(error.type());
} else if constexpr (WithoutCallback<FailCallback>) {
fail();
} else {
t_bad_callback(fail);
}
}).send();
}
template <typename DoneCallback, typename FailCallback>
mtpRequestId SuggestMessageOrMedia(
not_null<HistoryItem*> item,
const TextWithEntities &textWithEntities,
Data::WebPageDraft webpage,
SendOptions options,
DoneCallback &&done,
FailCallback &&fail,
std::optional<MTPInputMedia> inputMedia) {
const auto wasMedia = item->media();
if (!inputMedia && wasMedia && wasMedia->allowsEditCaption()) {
if (const auto photo = wasMedia->photo()) {
inputMedia = MTP_inputMediaPhoto(
MTP_flags(0),
photo->mtpInput(),
MTPint()); // ttl_seconds
} else if (const auto document = wasMedia->document()) {
inputMedia = MTP_inputMediaDocument(
MTP_flags(0),
document->mtpInput(),
MTPInputPhoto(), // video_cover
MTPint(), // video_timestamp
MTPint(), // ttl_seconds
MTPstring()); // query
}
}
if (inputMedia || (!webpage.removed && !webpage.url.isEmpty())) {
return SuggestMedia(
item,
textWithEntities,
webpage,
options,
std::move(done),
std::move(fail),
inputMedia);
}
return SuggestMessage(
item,
textWithEntities,
webpage,
options,
std::move(done),
std::move(fail));
}
template <typename DoneCallback, typename FailCallback>
mtpRequestId EditMessage(
not_null<HistoryItem*> item,
@@ -55,6 +281,18 @@ mtpRequestId EditMessage(
DoneCallback &&done,
FailCallback &&fail,
std::optional<MTPInputMedia> inputMedia = std::nullopt) {
if (item->computeSuggestionActions()
== SuggestionActions::AcceptAndDecline) {
return SuggestMessageOrMedia(
item,
textWithEntities,
webpage,
options,
std::move(done),
std::move(fail),
inputMedia);
}
const auto session = &item->history()->session();
const auto api = &session->api();
@@ -71,31 +309,31 @@ mtpRequestId EditMessage(
const auto emptyFlag = MTPmessages_EditMessage::Flag(0);
const auto flags = emptyFlag
| ((!text.isEmpty() || media)
? MTPmessages_EditMessage::Flag::f_message
: emptyFlag)
| ((media && inputMedia.has_value())
? MTPmessages_EditMessage::Flag::f_media
: emptyFlag)
| (webpage.removed
? MTPmessages_EditMessage::Flag::f_no_webpage
: emptyFlag)
| ((!webpage.removed && !webpage.url.isEmpty())
? MTPmessages_EditMessage::Flag::f_media
: emptyFlag)
| (((!webpage.removed && !webpage.url.isEmpty() && webpage.invert)
|| options.invertCaption)
? MTPmessages_EditMessage::Flag::f_invert_media
: emptyFlag)
| (!sentEntities.v.isEmpty()
? MTPmessages_EditMessage::Flag::f_entities
: emptyFlag)
| (options.scheduled
? MTPmessages_EditMessage::Flag::f_schedule_date
: emptyFlag)
| (item->isBusinessShortcut()
? MTPmessages_EditMessage::Flag::f_quick_reply_shortcut_id
: emptyFlag);
| ((!text.isEmpty() || media)
? MTPmessages_EditMessage::Flag::f_message
: emptyFlag)
| ((media && inputMedia.has_value())
? MTPmessages_EditMessage::Flag::f_media
: emptyFlag)
| (webpage.removed
? MTPmessages_EditMessage::Flag::f_no_webpage
: emptyFlag)
| ((!webpage.removed && !webpage.url.isEmpty())
? MTPmessages_EditMessage::Flag::f_media
: emptyFlag)
| (((!webpage.removed && !webpage.url.isEmpty() && webpage.invert)
|| options.invertCaption)
? MTPmessages_EditMessage::Flag::f_invert_media
: emptyFlag)
| (!sentEntities.v.isEmpty()
? MTPmessages_EditMessage::Flag::f_entities
: emptyFlag)
| (options.scheduled
? MTPmessages_EditMessage::Flag::f_schedule_date
: emptyFlag)
| (item->isBusinessShortcut()
? MTPmessages_EditMessage::Flag::f_quick_reply_shortcut_id
: emptyFlag);
const auto id = item->isScheduled()
? session->scheduledMessages().lookupId(item)

View File

@@ -9,8 +9,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "apiwrap.h"
#include "base/unixtime.h"
#include "chat_helpers/message_field.h"
#include "core/click_handler_types.h"
#include "data/data_changes.h"
#include "data/data_session.h"
#include "data/data_saved_sublist.h"
#include "history/view/history_view_suggest_options.h"
#include "history/history.h"
#include "history/history_item.h"
@@ -134,9 +137,8 @@ void RequestApprovalDate(
using namespace HistoryView;
auto dateBox = Box(ChooseSuggestTimeBox, SuggestTimeBoxArgs{
.session = &controller->session(),
.title = tr::lng_suggest_options_date(),
.submit = tr::lng_settings_save(),
.done = done,
.mode = SuggestMode::New,
});
*weak = dateBox.data();
controller->uiShow()->show(std::move(dateBox));
@@ -266,10 +268,9 @@ void SuggestApprovalDate(
using namespace HistoryView;
auto dateBox = Box(ChooseSuggestTimeBox, SuggestTimeBoxArgs{
.session = &controller->session(),
.title = tr::lng_suggest_menu_edit_time(),
.submit = tr::lng_profile_suggest_button(),
.done = done,
.value = suggestion->date,
.mode = SuggestMode::Change,
});
*weak = dateBox.data();
controller->uiShow()->show(std::move(dateBox));
@@ -311,6 +312,7 @@ void SuggestApprovalPrice(
.stars = uint32(suggestion->stars),
.date = suggestion->date,
},
.mode = SuggestMode::Change,
});
*weak = dateBox.data();
controller->uiShow()->show(std::move(dateBox));
@@ -373,9 +375,47 @@ std::shared_ptr<ClickHandler> SuggestChangesClickHandler(
const auto menu = Ui::CreateChild<Ui::PopupMenu>(
window->widget(),
st::popupMenuWithIcons);
menu->addAction(tr::lng_suggest_menu_edit_message(tr::now), [=] {
}, &st::menuIconEdit);
if (HistoryView::CanEditSuggestedMessage(item)) {
menu->addAction(tr::lng_suggest_menu_edit_message(tr::now), [=] {
const auto item = session->data().message(id);
if (!item) {
return;
}
const auto suggestion = item->Get<HistoryMessageSuggestedPost>();
if (!suggestion) {
return;
}
const auto history = item->history();
const auto editData = PrepareEditText(item);
const auto cursor = MessageCursor{
int(editData.text.size()),
int(editData.text.size()),
Ui::kQFixedMax
};
const auto monoforumPeerId = history->amMonoforumAdmin()
? item->sublistPeerId()
: PeerId();
const auto previewDraft = Data::WebPageDraft::FromItem(item);
history->setLocalEditDraft(std::make_unique<Data::Draft>(
editData,
FullReplyTo{
.messageId = FullMsgId(history->peer->id, item->id),
.monoforumPeerId = monoforumPeerId,
},
SuggestPostOptions{
.exists = 1,
.stars = uint32(suggestion->stars),
.date = suggestion->date,
},
cursor,
previewDraft));
history->session().changes().entryUpdated(
(monoforumPeerId
? item->savedSublist()
: (Data::Thread*)history.get()),
Data::EntryUpdate::Flag::LocalDraftSet);
}, &st::menuIconEdit);
}
menu->addAction(tr::lng_suggest_menu_edit_price(tr::now), [=] {
if (const auto item = session->data().message(id)) {
SuggestApprovalPrice(window, item);