mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2026-07-25 06:54:43 +00:00
feat: reply to deleted messages
This commit is contained in:
@@ -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(
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user