feat: reply to deleted messages

This commit is contained in:
AlexeyZavar
2026-02-06 22:51:17 +03:00
parent 48c7a4046c
commit cf72307b95
6 changed files with 183 additions and 12 deletions
+46
View File
@@ -308,6 +308,25 @@ void SendExistingDocument(
MessageToSend &&message,
not_null<DocumentData*> document,
std::optional<MsgId> localMessageId) {
if (!document->sticker()
&& !document->isVideoMessage()
&& !document->isVoiceMessage()) {
const auto clearReplyTo = prependPseudoReply(message);
if (clearReplyTo) {
message.action.replyTo.messageId = FullMsgId(
message.action.replyTo.messageId.peer,
message.action.replyTo.topicRootId);
}
} else if (message.action.replyTo && message.action.history) {
if (const auto item = message.action.history->session().data().message(message.action.replyTo.messageId)) {
if (item->isDeleted()) {
message.action.replyTo.messageId = FullMsgId(
message.action.replyTo.messageId.peer,
message.action.replyTo.topicRootId);
}
}
}
const auto inputMedia = [=] {
return MTP_inputMediaDocument(
MTP_flags(message.action.options.mediaSpoiler
@@ -335,6 +354,13 @@ void SendExistingPhoto(
MessageToSend &&message,
not_null<PhotoData*> photo,
std::optional<MsgId> localMessageId) {
const auto clearReplyTo = prependPseudoReply(message);
if (clearReplyTo) {
message.action.replyTo.messageId = FullMsgId(
message.action.replyTo.messageId.peer,
message.action.replyTo.topicRootId);
}
const auto inputMedia = [=] {
return MTP_inputMediaPhoto(
MTP_flags(0),
@@ -558,6 +584,26 @@ void SendConfirmedFile(
const auto history = session->data().history(file->to.peer);
const auto peer = history->peer;
if (!isEditing
&& file->type != SendMediaType::Audio
&& file->type != SendMediaType::Round) {
const auto clearReplyTo = prependPseudoReply(
session, history, file->caption, file->to.replyTo);
if (clearReplyTo) {
file->to.replyTo.messageId = FullMsgId(
file->to.replyTo.messageId.peer,
file->to.replyTo.topicRootId);
}
} else if (!isEditing && file->to.replyTo) {
if (const auto item = session->data().message(file->to.replyTo.messageId)) {
if (item->isDeleted()) {
file->to.replyTo.messageId = FullMsgId(
file->to.replyTo.messageId.peer,
file->to.replyTo.topicRootId);
}
}
}
if (!isEditing) {
const auto histories = &session->data().histories();
file->to.replyTo.messageId = histories->convertTopicReplyToId(
+6
View File
@@ -4008,6 +4008,7 @@ void ApiWrap::sendMessage(
MessageToSend &&message,
std::optional<MsgId> localMessageId) {
applyGhostScheduling(_session, message.action.options);
const auto clearReplyTo = prependPseudoReply(message);
const auto history = message.action.history;
const auto peer = history->peer;
@@ -4034,6 +4035,11 @@ void ApiWrap::sendMessage(
? Data::CanSendTexts(topic)
: Data::CanSendTexts(peer);
if (clearReplyTo) {
message.action.replyTo.messageId = FullMsgId(message.action.replyTo.messageId.peer, message.action.replyTo.topicRootId);
action.replyTo.messageId = FullMsgId(action.replyTo.messageId.peer, action.replyTo.topicRootId);
}
if ((!canSendTexts && !AyuForward::isForwarding(peer->id)) || Api::SendDice(message)) {
return;
}
@@ -924,6 +924,125 @@ bool mediaDownloadable(const Data::Media *media) {
return true;
}
static bool prependPseudoReplyImpl(
not_null<Main::Session*> session,
not_null<History*> history,
TextWithTags &textWithTags,
FullReplyTo &replyTo) {
if (!replyTo) {
return false;
}
const auto replyItem = session->data().message(replyTo.messageId);
if (!replyItem || !replyItem->isDeleted()) {
return false;
}
const auto shortify = [&](const QString &text, int maxLength) {
if (text.isEmpty() || text.length() < maxLength) {
return text;
}
return text.left(maxLength - 1) + QChar(8230); // …
};
const auto shiftEntities = [&](QVector<TextWithTags::Tag> &tags, int offset) {
if (tags.isEmpty() || !offset) {
return;
}
for (auto &tag : tags) {
tag.offset += offset;
}
};
const auto from = replyItem->from();
auto name = QString();
if (!history->peer->isUser() || replyItem->history()->peer != history->peer) {
name = from->name();
}
auto msgText = !replyTo.quote.empty()
? replyTo.quote.text
: replyItem->originalText().text;
if (msgText.isEmpty()) {
msgText = replyItem->notificationText().text;
}
const auto shortifiedText = shortify(msgText, 100);
const auto prefix = name.isEmpty()
? shortifiedText
: (name + "\n" + shortifiedText);
if (textWithTags.empty()) {
textWithTags.text = prefix;
} else {
textWithTags.text.prepend(prefix + "\n");
}
const auto prefixLength = prefix.length() + (textWithTags.text.length() > prefix.length() ? 1 : 0);
shiftEntities(textWithTags.tags, prefixLength);
EntitiesInText newEntities;
const auto nameLength = name.length();
newEntities.push_back(EntityInText{
EntityType::Blockquote,
0,
prefix.length(),
{}
});
if (nameLength > 0) {
newEntities.push_back(EntityInText{
EntityType::Bold,
0,
nameLength,
QString()
});
auto accessHash = uint64(0);
if (const auto user = from->asUser()) {
accessHash = user->accessHash();
} else if (const auto channel = from->asChannel()) {
accessHash = channel->accessHash();
}
if (accessHash != 0) {
const auto mentionData = QStringLiteral("%1.%2:%3")
.arg(from->id.value)
.arg(accessHash)
.arg(session->userId().bare);
newEntities.push_back(EntityInText{
EntityType::MentionName,
0,
nameLength,
mentionData
});
}
}
const auto newTags = TextUtilities::ConvertEntitiesToTextTags(newEntities);
textWithTags.tags.append(newTags);
return true;
}
bool prependPseudoReply(Api::MessageToSend &message) {
if (!message.action.history) {
return false;
}
return prependPseudoReplyImpl(
&message.action.history->session(),
message.action.history,
message.textWithTags,
message.action.replyTo);
}
bool prependPseudoReply(
not_null<Main::Session*> session,
not_null<History*> history,
TextWithTags &caption,
FullReplyTo &replyTo) {
return prependPseudoReplyImpl(session, history, caption, replyTo);
}
TextWithEntities reverseLocalPremiumEmoji(const TextWithEntities &text, not_null<History *> history, bool isForQuote) {
if (text.empty()) {
return text;
@@ -7,6 +7,7 @@
#pragma once
#include "rc_manager.h"
#include "api/api_common.h"
#include "ayu/data/entities.h"
#include "core/application.h"
@@ -113,6 +114,13 @@ PeerData* getPeerFromDialogId(unsigned long long id);
QString filterZalgo(const QString &text);
bool prependPseudoReply(Api::MessageToSend &message);
bool prependPseudoReply(
not_null<Main::Session*> session,
not_null<History*> history,
TextWithTags &caption,
FullReplyTo &replyTo);
void getRegistrationDate(not_null<PeerData*> peer, Fn<void(TextWithEntities)> callback);
void applyGhostScheduling(
@@ -668,7 +668,7 @@ void HistoryInner::setupSwipeReplyAndBack() {
}
const auto item = view->data();
const auto canSendReply = CanSendReply(item);
const auto canReply = canSendReply || (item->allowsForward() && !item->isDeleted());
const auto canReply = canSendReply || item->allowsForward();
if (!canReply) {
return true;
}
@@ -2312,9 +2312,7 @@ void HistoryInner::mouseDoubleClickEvent(QMouseEvent *e) {
mouseActionCancel();
switch (HistoryView::CurrentQuickAction()) {
case HistoryView::DoubleClickQuickAction::Reply: {
if (!view->data()->isDeleted()) {
_widget->replyToMessage(view->data());
}
_widget->replyToMessage(view->data());
} break;
case HistoryView::DoubleClickQuickAction::React: {
toggleFavoriteReaction(view);
@@ -2821,7 +2819,7 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) {
return;
}
const auto canSendReply = CanSendReply(item);
const auto canReply = canSendReply || (item->allowsForward() && !item->isDeleted());
const auto canReply = canSendReply || item->allowsForward();
if (canReply) {
const auto selected = selectedQuote(item);
auto text = (selected
@@ -5237,10 +5235,6 @@ auto HistoryInner::DelegateMixin()
}
bool CanSendReply(not_null<const HistoryItem*> item) {
if (item->isDeleted()) {
return false;
}
const auto peer = item->history()->peer;
if (const auto topic = item->topic()) {
return Data::CanSendAnything(topic);
@@ -2757,9 +2757,7 @@ void ListWidget::mouseDoubleClickEvent(QMouseEvent *e) {
mouseActionCancel();
switch (CurrentQuickAction()) {
case DoubleClickQuickAction::Reply: {
if (!_overElement->data()->isDeleted()) {
replyToMessageRequestNotify({ _overElement->data()->fullId() });
}
replyToMessageRequestNotify({ _overElement->data()->fullId() });
} break;
case DoubleClickQuickAction::React: {
toggleFavoriteReaction(_overElement);