mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2026-08-06 03:42:56 +00:00
Added ephemeral messages data component with receive and send support.
This commit is contained in:
@@ -591,6 +591,8 @@ PRIVATE
|
||||
data/business/data_shortcut_messages.h
|
||||
data/components/credits.cpp
|
||||
data/components/credits.h
|
||||
data/components/ephemeral_messages.cpp
|
||||
data/components/ephemeral_messages.h
|
||||
data/components/factchecks.cpp
|
||||
data/components/factchecks.h
|
||||
data/components/gift_auctions.cpp
|
||||
|
||||
@@ -4917,6 +4917,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_from_you" = "You";
|
||||
"lng_from_draft" = "Draft";
|
||||
"lng_bot_description" = "What can this bot do?";
|
||||
"lng_ephemeral_visible_you" = "Only visible to you";
|
||||
"lng_ephemeral_visible_to" = "Only visible to {user}";
|
||||
"lng_ephemeral_about" = "Only you see this message. It will disappear after you restart the app.";
|
||||
"lng_ephemeral_command_tooltip" = "Other members won't see this message";
|
||||
"lng_ephemeral_reply_text_only" = "Only text messages can be sent in reply to this message.";
|
||||
"lng_ephemeral_cant_schedule" = "Ephemeral messages can't be scheduled.";
|
||||
"lng_unblock_button" = "Unblock";
|
||||
"lng_restart_button" = "Restart";
|
||||
"lng_channel_mute" = "Mute";
|
||||
|
||||
@@ -24,6 +24,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "chat_helpers/stickers_dice_pack.h"
|
||||
#include "data/business/data_shortcut_messages.h"
|
||||
#include "data/components/credits.h"
|
||||
#include "data/components/ephemeral_messages.h"
|
||||
#include "data/components/gift_auctions.h"
|
||||
#include "data/components/promo_suggestions.h"
|
||||
#include "data/components/scheduled_messages.h"
|
||||
@@ -1877,6 +1878,21 @@ void Updates::feedUpdate(const MTPUpdate &update) {
|
||||
session().scheduledMessages().apply(d);
|
||||
} break;
|
||||
|
||||
case mtpc_updateNewEphemeralMessage: {
|
||||
const auto &d = update.c_updateNewEphemeralMessage();
|
||||
session().ephemeralMessages().apply(d);
|
||||
} break;
|
||||
|
||||
case mtpc_updateEditEphemeralMessage: {
|
||||
const auto &d = update.c_updateEditEphemeralMessage();
|
||||
session().ephemeralMessages().apply(d);
|
||||
} break;
|
||||
|
||||
case mtpc_updateDeleteEphemeralMessages: {
|
||||
const auto &d = update.c_updateDeleteEphemeralMessages();
|
||||
session().ephemeralMessages().apply(d);
|
||||
} break;
|
||||
|
||||
case mtpc_updateQuickReplies: {
|
||||
const auto &d = update.c_updateQuickReplies();
|
||||
session().data().shortcutMessages().apply(d);
|
||||
|
||||
@@ -42,6 +42,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "api/api_websites.h"
|
||||
#include "data/business/data_shortcut_messages.h"
|
||||
#include "data/components/credits.h"
|
||||
#include "data/components/ephemeral_messages.h"
|
||||
#include "data/components/scheduled_messages.h"
|
||||
#include "data/notify/data_notify_settings.h"
|
||||
#include "data/data_changes.h"
|
||||
@@ -2342,6 +2343,12 @@ mtpRequestId ApiWrap::savePreparedDraftToCloud(
|
||||
})
|
||||
: std::shared_ptr<Callbacks>();
|
||||
|
||||
auto reply = draft.reply;
|
||||
const auto replyItem = _session->data().message(reply.messageId);
|
||||
if (replyItem && replyItem->isEphemeral()) {
|
||||
reply.messageId = FullMsgId();
|
||||
}
|
||||
|
||||
auto flags = MTPmessages_SaveDraft::Flags(0);
|
||||
const auto &textWithTags = draft.textWithTags;
|
||||
if (draft.webpage.removed) {
|
||||
@@ -2349,9 +2356,9 @@ mtpRequestId ApiWrap::savePreparedDraftToCloud(
|
||||
} else if (!draft.webpage.url.isEmpty()) {
|
||||
flags |= MTPmessages_SaveDraft::Flag::f_media;
|
||||
}
|
||||
if (draft.reply.messageId
|
||||
|| draft.reply.topicRootId
|
||||
|| draft.reply.monoforumPeerId) {
|
||||
if (reply.messageId
|
||||
|| reply.topicRootId
|
||||
|| reply.monoforumPeerId) {
|
||||
flags |= MTPmessages_SaveDraft::Flag::f_reply_to;
|
||||
}
|
||||
if (!textWithTags.tags.isEmpty()) {
|
||||
@@ -2454,7 +2461,7 @@ mtpRequestId ApiWrap::savePreparedDraftToCloud(
|
||||
bool refreshed) -> mtpRequestId {
|
||||
const auto requestId = request(MTPmessages_SaveDraft(
|
||||
MTP_flags(flags),
|
||||
ReplyToForMTP(history, draft.reply),
|
||||
ReplyToForMTP(history, reply),
|
||||
history->peer->input(),
|
||||
MTP_string(textWithTags.text),
|
||||
entities,
|
||||
@@ -4181,7 +4188,8 @@ void ApiWrap::sendFiles(
|
||||
Ui::PreparedList &&list,
|
||||
SendMediaType type,
|
||||
std::shared_ptr<SendingAlbum> album,
|
||||
const SendAction &action) {
|
||||
SendAction action) {
|
||||
StripEphemeralReply(_session, action.replyTo);
|
||||
const auto to = FileLoadTaskOptions(action);
|
||||
if (album) {
|
||||
album->options = to.options;
|
||||
@@ -4569,8 +4577,15 @@ void ApiWrap::sendMessage(
|
||||
? replyTo->topicRootId()
|
||||
: Data::ForumTopic::kGeneralId;
|
||||
const auto topic = peer->forumTopicFor(topicRootId);
|
||||
if (!(topic ? Data::CanSendTexts(topic) : Data::CanSendTexts(peer))
|
||||
|| Api::SendDice(message)) {
|
||||
if (!(topic ? Data::CanSendTexts(topic) : Data::CanSendTexts(peer))) {
|
||||
return;
|
||||
} else if (_session->ephemeralMessages().trySend(message)) {
|
||||
if (clearCloudDraft) {
|
||||
history->clearCloudDraft(draftTopicRootId, draftMonoforumPeerId);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (Api::SendDice(message)) {
|
||||
return;
|
||||
}
|
||||
local().saveRecentSentHashtags(textWithTags.text);
|
||||
@@ -4859,6 +4874,7 @@ void ApiWrap::sendInlineResult(
|
||||
SendAction action,
|
||||
std::optional<MsgId> localMessageId,
|
||||
Fn<void(bool)> done) {
|
||||
StripEphemeralReply(_session, action.replyTo);
|
||||
sendAction(action);
|
||||
|
||||
const auto history = action.history;
|
||||
|
||||
@@ -364,7 +364,7 @@ public:
|
||||
Ui::PreparedList &&list,
|
||||
SendMediaType type,
|
||||
std::shared_ptr<SendingAlbum> album,
|
||||
const SendAction &action);
|
||||
SendAction action);
|
||||
void sendFile(
|
||||
const QByteArray &fileContent,
|
||||
SendMediaType type,
|
||||
|
||||
@@ -0,0 +1,462 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "data/components/ephemeral_messages.h"
|
||||
|
||||
#include "api/api_common.h"
|
||||
#include "api/api_text_entities.h"
|
||||
#include "apiwrap.h"
|
||||
#include "base/random.h"
|
||||
#include "base/unixtime.h"
|
||||
#include "data/data_channel.h"
|
||||
#include "data/data_chat.h"
|
||||
#include "data/data_peer_bot_command.h"
|
||||
#include "data/data_session.h"
|
||||
#include "data/data_user.h"
|
||||
#include "history/history.h"
|
||||
#include "history/history_item.h"
|
||||
#include "history/history_item_edition.h"
|
||||
#include "main/main_session.h"
|
||||
|
||||
namespace Data {
|
||||
namespace {
|
||||
|
||||
constexpr auto kKeepDuration = TimeId(2 * 86400);
|
||||
constexpr auto kPruneEach = 3600 * crl::time(1000);
|
||||
constexpr auto kPendingFlushDelay = 2 * crl::time(1000);
|
||||
|
||||
struct ParsedCommand {
|
||||
QString command;
|
||||
QString username;
|
||||
};
|
||||
|
||||
[[nodiscard]] std::optional<ParsedCommand> ParseCommand(
|
||||
const QString &text) {
|
||||
if (!text.startsWith(QChar('/'))) {
|
||||
return std::nullopt;
|
||||
}
|
||||
const auto size = int(text.size());
|
||||
const auto good = [](QChar ch) {
|
||||
return ch.isLetterOrNumber() || (ch == QChar('_'));
|
||||
};
|
||||
auto i = 1;
|
||||
while (i < size && good(text[i])) {
|
||||
++i;
|
||||
}
|
||||
auto result = ParsedCommand{ .command = text.mid(1, i - 1) };
|
||||
if (result.command.isEmpty()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
if (i < size && text[i] == QChar('@')) {
|
||||
const auto from = ++i;
|
||||
while (i < size && good(text[i])) {
|
||||
++i;
|
||||
}
|
||||
result.username = text.mid(from, i - from);
|
||||
if (result.username.isEmpty()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
if (i < size && !text[i].isSpace()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
EphemeralMessages::EphemeralMessages(not_null<Main::Session*> session)
|
||||
: _session(session)
|
||||
, _pruneTimer([=] { pruneOld(); })
|
||||
, _pendingTimer([=] { drainPending(true); }) {
|
||||
_session->data().itemRemoved(
|
||||
) | rpl::on_next([=](not_null<const HistoryItem*> item) {
|
||||
if (item->isEphemeral()) {
|
||||
itemRemoved(item);
|
||||
}
|
||||
}, _lifetime);
|
||||
|
||||
_pruneTimer.callEach(kPruneEach);
|
||||
}
|
||||
|
||||
EphemeralMessages::~EphemeralMessages() = default;
|
||||
|
||||
void EphemeralMessages::apply(const MTPDupdateNewEphemeralMessage &update) {
|
||||
applyOrDefer(update.vmessage());
|
||||
}
|
||||
|
||||
void EphemeralMessages::applyOrDefer(const MTPEphemeralMessage &message) {
|
||||
if (replyTargetMissing(message.data())) {
|
||||
_pending.push_back(message);
|
||||
_pendingTimer.callOnce(kPendingFlushDelay);
|
||||
return;
|
||||
}
|
||||
applyNew(message.data());
|
||||
drainPending();
|
||||
}
|
||||
|
||||
bool EphemeralMessages::replyTargetMissing(
|
||||
const MTPDephemeralMessage &data) const {
|
||||
const auto header = data.vreply_to();
|
||||
if (!header) {
|
||||
return false;
|
||||
}
|
||||
return header->match([&](const MTPDmessageReplyHeader &reply) {
|
||||
const auto replyToId = reply.vreply_to_msg_id();
|
||||
if (!reply.is_reply_to_ephemeral() || !replyToId) {
|
||||
return false;
|
||||
}
|
||||
const auto history = _session->data().history(
|
||||
peerFromMTP(data.vpeer_id()));
|
||||
return !lookupItem(history->peer, replyToId->v);
|
||||
}, [](const MTPDmessageReplyStoryHeader &) {
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
void EphemeralMessages::drainPending(bool force) {
|
||||
while (!_pending.empty()) {
|
||||
const auto i = ranges::find_if(_pending, [&](
|
||||
const MTPEphemeralMessage &message) {
|
||||
return force || !replyTargetMissing(message.data());
|
||||
});
|
||||
if (i == end(_pending)) {
|
||||
return;
|
||||
}
|
||||
const auto message = *i;
|
||||
_pending.erase(i);
|
||||
applyNew(message.data());
|
||||
}
|
||||
}
|
||||
|
||||
void EphemeralMessages::apply(const MTPDupdateEditEphemeralMessage &update) {
|
||||
const auto &data = update.vmessage().data();
|
||||
const auto history = _session->data().history(
|
||||
peerFromMTP(data.vpeer_id()));
|
||||
const auto item = lookupItem(history->peer, data.vid().v);
|
||||
if (!item) {
|
||||
applyNew(data);
|
||||
return;
|
||||
}
|
||||
auto edition = HistoryMessageEdition();
|
||||
edition.isEditHide = true;
|
||||
edition.editDate = -1;
|
||||
edition.useSameViews = true;
|
||||
edition.useSameForwards = true;
|
||||
edition.useSameReplies = true;
|
||||
edition.useSameReactions = true;
|
||||
edition.useSameSuggest = true;
|
||||
edition.textWithEntities = {
|
||||
qs(data.vmessage()),
|
||||
Api::EntitiesFromMTP(
|
||||
_session,
|
||||
data.ventities().value_or_empty()),
|
||||
};
|
||||
edition.replyMarkup = HistoryMessageMarkupData(data.vreply_markup());
|
||||
edition.mtpMedia = data.vmedia();
|
||||
item->applyEdition(std::move(edition));
|
||||
}
|
||||
|
||||
void EphemeralMessages::apply(
|
||||
const MTPDupdateDeleteEphemeralMessages &update) {
|
||||
const auto history = _session->data().historyLoaded(
|
||||
peerFromMTP(update.vpeer()));
|
||||
if (!history) {
|
||||
return;
|
||||
}
|
||||
for (const auto &id : update.vids().v) {
|
||||
if (const auto item = lookupItem(history->peer, id.v)) {
|
||||
item->destroy();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
HistoryItem *EphemeralMessages::applyNew(const MTPDephemeralMessage &data) {
|
||||
const auto history = _session->data().history(
|
||||
peerFromMTP(data.vpeer_id()));
|
||||
const auto ephemeralId = data.vid().v;
|
||||
if (const auto existing = lookupItem(history->peer, ephemeralId)) {
|
||||
return existing;
|
||||
}
|
||||
const auto fromId = peerFromMTP(data.vfrom_id());
|
||||
auto replyTo = FullReplyTo();
|
||||
if (const auto topMsgId = data.vtop_msg_id()) {
|
||||
replyTo.topicRootId = topMsgId->v;
|
||||
}
|
||||
if (const auto header = data.vreply_to()) {
|
||||
header->match([&](const MTPDmessageReplyHeader &reply) {
|
||||
const auto replyToId = reply.vreply_to_msg_id();
|
||||
if (reply.is_reply_to_ephemeral() && replyToId) {
|
||||
const auto target = lookupItem(history->peer, replyToId->v);
|
||||
if (target) {
|
||||
replyTo.messageId = target->fullId();
|
||||
}
|
||||
}
|
||||
}, [](const MTPDmessageReplyStoryHeader &) {
|
||||
});
|
||||
}
|
||||
const auto item = history->addNewLocalMessage(
|
||||
{
|
||||
.id = _session->data().nextLocalMessageId(),
|
||||
.flags = (MessageFlag::HistoryEntry
|
||||
| MessageFlag::Ephemeral
|
||||
| (data.is_out() ? MessageFlag::Outgoing : MessageFlag())
|
||||
| (fromId ? MessageFlag::HasFromId : MessageFlag())
|
||||
| (replyTo.messageId
|
||||
? MessageFlag::HasReplyInfo
|
||||
: MessageFlag())
|
||||
| (data.vreply_markup()
|
||||
? MessageFlag::HasReplyMarkup
|
||||
: MessageFlag())),
|
||||
.from = fromId,
|
||||
.replyTo = replyTo,
|
||||
.date = data.vdate().v,
|
||||
.markup = HistoryMessageMarkupData(data.vreply_markup()),
|
||||
},
|
||||
TextWithEntities {
|
||||
qs(data.vmessage()),
|
||||
Api::EntitiesFromMTP(
|
||||
_session,
|
||||
data.ventities().value_or_empty()),
|
||||
},
|
||||
data.vmedia()
|
||||
? *data.vmedia()
|
||||
: MTPMessageMedia(MTP_messageMediaEmpty()));
|
||||
_data[history].push_back({
|
||||
.ephemeralId = ephemeralId,
|
||||
.receiverId = UserId(data.vreceiver_id()),
|
||||
.item = item,
|
||||
});
|
||||
_session->data().requestItemResize(item);
|
||||
return item;
|
||||
}
|
||||
|
||||
HistoryItem *EphemeralMessages::lookupItem(
|
||||
not_null<PeerData*> peer,
|
||||
int32 ephemeralId) const {
|
||||
const auto history = _session->data().historyLoaded(peer);
|
||||
if (!history) {
|
||||
return nullptr;
|
||||
}
|
||||
const auto i = _data.find(history);
|
||||
if (i == end(_data)) {
|
||||
return nullptr;
|
||||
}
|
||||
const auto j = ranges::find(
|
||||
i->second,
|
||||
ephemeralId,
|
||||
&Entry::ephemeralId);
|
||||
return (j != end(i->second)) ? j->item.get() : nullptr;
|
||||
}
|
||||
|
||||
int32 EphemeralMessages::lookupId(not_null<const HistoryItem*> item) const {
|
||||
const auto entry = findByItem(item);
|
||||
return entry ? entry->ephemeralId : 0;
|
||||
}
|
||||
|
||||
UserData *EphemeralMessages::replyReceiver(
|
||||
not_null<const HistoryItem*> item) const {
|
||||
if (const auto entry = findByItem(item)) {
|
||||
return botForSending(*entry);
|
||||
}
|
||||
if (item->out() && item->isEphemeral()) {
|
||||
const auto target = _session->data().message(
|
||||
item->replyTo().messageId);
|
||||
if (target && target->isEphemeral() && !target->out()) {
|
||||
const auto from = target->from();
|
||||
return from ? from->asUser() : nullptr;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool EphemeralMessages::wouldSend(const Api::MessageToSend &message) const {
|
||||
const auto history = message.action.history;
|
||||
const auto peer = history->peer;
|
||||
if (!peer->isChat() && !peer->isMegagroup()) {
|
||||
return false;
|
||||
}
|
||||
const auto replyTo = _session->data().message(
|
||||
message.action.replyTo.messageId);
|
||||
if (replyTo && replyTo->isEphemeral()) {
|
||||
return true;
|
||||
} else if (message.action.replyTo) {
|
||||
return false;
|
||||
}
|
||||
return findCommandBot(peer, message.textWithTags.text.trimmed())
|
||||
!= nullptr;
|
||||
}
|
||||
|
||||
bool EphemeralMessages::trySend(const Api::MessageToSend &message) {
|
||||
const auto history = message.action.history;
|
||||
const auto peer = history->peer;
|
||||
if (!peer->isChat() && !peer->isMegagroup()) {
|
||||
return false;
|
||||
} else if (message.action.options.scheduled
|
||||
|| message.action.options.shortcutId) {
|
||||
if (wouldSend(message)) {
|
||||
LOG(("API Error: "
|
||||
"Dropping a scheduled ephemeral message send."));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
auto text = TextWithEntities{
|
||||
message.textWithTags.text,
|
||||
TextUtilities::ConvertTextTagsToEntities(message.textWithTags.tags),
|
||||
};
|
||||
TextUtilities::Trim(text);
|
||||
if (text.text.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
const auto replyToId = message.action.replyTo.messageId;
|
||||
if (const auto replyTo = _session->data().message(replyToId)) {
|
||||
if (replyTo->isEphemeral()) {
|
||||
const auto entry = findByItem(replyTo);
|
||||
const auto bot = entry ? botForSending(*entry) : nullptr;
|
||||
if (bot) {
|
||||
send(history, bot, std::move(text), entry->ephemeralId);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (message.action.replyTo) {
|
||||
return false;
|
||||
}
|
||||
const auto bot = findCommandBot(peer, text.text);
|
||||
if (!bot) {
|
||||
return false;
|
||||
}
|
||||
send(history, bot, std::move(text));
|
||||
return true;
|
||||
}
|
||||
|
||||
UserData *EphemeralMessages::findCommandBot(
|
||||
not_null<PeerData*> peer,
|
||||
const QString &text) const {
|
||||
const auto parsed = ParseCommand(text);
|
||||
if (!parsed) {
|
||||
return nullptr;
|
||||
}
|
||||
const auto &commands = peer->isChat()
|
||||
? peer->asChat()->botCommands()
|
||||
: peer->asMegagroup()->mgInfo->botCommands();
|
||||
for (const auto &[userId, list] : commands) {
|
||||
const auto user = _session->data().userLoaded(userId);
|
||||
if (!user
|
||||
|| (!parsed->username.isEmpty()
|
||||
&& parsed->username.compare(
|
||||
user->username(),
|
||||
Qt::CaseInsensitive) != 0)) {
|
||||
continue;
|
||||
}
|
||||
for (const auto &command : list) {
|
||||
if (command.ephemeral
|
||||
&& !command.command.compare(
|
||||
parsed->command,
|
||||
Qt::CaseInsensitive)) {
|
||||
return user;
|
||||
}
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void EphemeralMessages::send(
|
||||
not_null<History*> history,
|
||||
not_null<UserData*> bot,
|
||||
TextWithEntities text,
|
||||
int32 replyToEphemeralId) {
|
||||
const auto session = _session;
|
||||
const auto entities = Api::EntitiesToMTP(
|
||||
session,
|
||||
text.entities,
|
||||
Api::ConvertOption::SkipLocal);
|
||||
using Flag = MTPephemeral_SendMessage::Flag;
|
||||
const auto flags = Flag(0)
|
||||
| (entities.v.isEmpty() ? Flag(0) : Flag::f_entities)
|
||||
| (replyToEphemeralId ? Flag::f_reply_to : Flag(0));
|
||||
session->api().request(MTPephemeral_SendMessage(
|
||||
MTP_flags(flags),
|
||||
history->peer->input(),
|
||||
bot->inputUser(),
|
||||
MTPlong(), // query_id
|
||||
MTP_string(text.text),
|
||||
entities,
|
||||
MTPInputMedia(),
|
||||
MTPReplyMarkup(),
|
||||
MTPInputRichMessage(),
|
||||
MTP_long(base::RandomValue<uint64>()),
|
||||
MTP_inputReplyToEphemeralMessage(MTP_int(replyToEphemeralId))
|
||||
)).done([=](const MTPUpdates &result) {
|
||||
session->api().applyUpdates(result);
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
LOG(("API Error: send ephemeral message - %1").arg(error.type()));
|
||||
}).send();
|
||||
}
|
||||
|
||||
void EphemeralMessages::deleteMessage(not_null<HistoryItem*> item) {
|
||||
if (const auto entry = findByItem(item)) {
|
||||
const auto receiver = _session->data().user(entry->receiverId);
|
||||
_session->api().request(MTPephemeral_DeleteMessage(
|
||||
item->history()->peer->input(),
|
||||
receiver->inputUser(),
|
||||
MTP_int(entry->ephemeralId)
|
||||
)).send();
|
||||
}
|
||||
item->destroy();
|
||||
}
|
||||
|
||||
const EphemeralMessages::Entry *EphemeralMessages::findByItem(
|
||||
not_null<const HistoryItem*> item) const {
|
||||
const auto i = _data.find(item->history());
|
||||
if (i == end(_data)) {
|
||||
return nullptr;
|
||||
}
|
||||
const auto j = ranges::find(i->second, item.get(), &Entry::item);
|
||||
return (j != end(i->second)) ? &*j : nullptr;
|
||||
}
|
||||
|
||||
UserData *EphemeralMessages::botForSending(const Entry &entry) const {
|
||||
if (entry.item->out()) {
|
||||
return _session->data().userLoaded(entry.receiverId);
|
||||
}
|
||||
const auto from = entry.item->from();
|
||||
return from ? from->asUser() : nullptr;
|
||||
}
|
||||
|
||||
void EphemeralMessages::itemRemoved(not_null<const HistoryItem*> item) {
|
||||
const auto i = _data.find(item->history());
|
||||
if (i == end(_data)) {
|
||||
return;
|
||||
}
|
||||
const auto j = ranges::find(i->second, item.get(), &Entry::item);
|
||||
if (j == end(i->second)) {
|
||||
return;
|
||||
}
|
||||
i->second.erase(j);
|
||||
if (i->second.empty()) {
|
||||
_data.erase(i);
|
||||
}
|
||||
}
|
||||
|
||||
void EphemeralMessages::pruneOld() {
|
||||
const auto till = base::unixtime::now() - kKeepDuration;
|
||||
auto old = std::vector<not_null<HistoryItem*>>();
|
||||
for (const auto &[history, list] : _data) {
|
||||
for (const auto &entry : list) {
|
||||
if (entry.item->date() <= till) {
|
||||
old.push_back(entry.item);
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const auto &item : old) {
|
||||
item->destroy();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Data
|
||||
@@ -0,0 +1,87 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "base/timer.h"
|
||||
|
||||
class History;
|
||||
class HistoryItem;
|
||||
class PeerData;
|
||||
class UserData;
|
||||
|
||||
namespace Api {
|
||||
struct MessageToSend;
|
||||
} // namespace Api
|
||||
|
||||
namespace Main {
|
||||
class Session;
|
||||
} // namespace Main
|
||||
|
||||
namespace Data {
|
||||
|
||||
class EphemeralMessages final {
|
||||
public:
|
||||
explicit EphemeralMessages(not_null<Main::Session*> session);
|
||||
EphemeralMessages(const EphemeralMessages &other) = delete;
|
||||
EphemeralMessages &operator=(const EphemeralMessages &other) = delete;
|
||||
~EphemeralMessages();
|
||||
|
||||
void apply(const MTPDupdateNewEphemeralMessage &update);
|
||||
void apply(const MTPDupdateEditEphemeralMessage &update);
|
||||
void apply(const MTPDupdateDeleteEphemeralMessages &update);
|
||||
|
||||
[[nodiscard]] HistoryItem *lookupItem(
|
||||
not_null<PeerData*> peer,
|
||||
int32 ephemeralId) const;
|
||||
[[nodiscard]] int32 lookupId(not_null<const HistoryItem*> item) const;
|
||||
[[nodiscard]] UserData *replyReceiver(
|
||||
not_null<const HistoryItem*> item) const;
|
||||
|
||||
[[nodiscard]] bool wouldSend(const Api::MessageToSend &message) const;
|
||||
[[nodiscard]] bool trySend(const Api::MessageToSend &message);
|
||||
void send(
|
||||
not_null<History*> history,
|
||||
not_null<UserData*> bot,
|
||||
TextWithEntities text,
|
||||
int32 replyToEphemeralId = 0);
|
||||
void deleteMessage(not_null<HistoryItem*> item);
|
||||
|
||||
private:
|
||||
struct Entry {
|
||||
int32 ephemeralId = 0;
|
||||
UserId receiverId;
|
||||
not_null<HistoryItem*> item;
|
||||
};
|
||||
using List = std::vector<Entry>;
|
||||
|
||||
void applyOrDefer(const MTPEphemeralMessage &message);
|
||||
HistoryItem *applyNew(const MTPDephemeralMessage &data);
|
||||
[[nodiscard]] UserData *findCommandBot(
|
||||
not_null<PeerData*> peer,
|
||||
const QString &text) const;
|
||||
[[nodiscard]] bool replyTargetMissing(
|
||||
const MTPDephemeralMessage &data) const;
|
||||
void drainPending(bool force = false);
|
||||
[[nodiscard]] const Entry *findByItem(
|
||||
not_null<const HistoryItem*> item) const;
|
||||
[[nodiscard]] UserData *botForSending(const Entry &entry) const;
|
||||
void itemRemoved(not_null<const HistoryItem*> item);
|
||||
void pruneOld();
|
||||
|
||||
const not_null<Main::Session*> _session;
|
||||
|
||||
base::Timer _pruneTimer;
|
||||
base::Timer _pendingTimer;
|
||||
base::flat_map<not_null<History*>, List> _data;
|
||||
std::vector<MTPEphemeralMessage> _pending;
|
||||
|
||||
rpl::lifetime _lifetime;
|
||||
|
||||
};
|
||||
|
||||
} // namespace Data
|
||||
@@ -14,6 +14,7 @@ BotCommand BotCommandFromTL(const MTPBotCommand &result) {
|
||||
return BotCommand {
|
||||
.command = qs(data.vcommand().v),
|
||||
.description = qs(data.vdescription().v),
|
||||
.ephemeral = data.is_ephemeral(),
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ namespace Data {
|
||||
struct BotCommand final {
|
||||
QString command;
|
||||
QString description;
|
||||
bool ephemeral = false;
|
||||
|
||||
friend inline bool operator==(
|
||||
const BotCommand &,
|
||||
|
||||
@@ -370,6 +370,8 @@ enum class MessageFlag : uint64 {
|
||||
TextAppearingStarted = (1ULL << 61),
|
||||
|
||||
GuestChatViaFrom = (1ULL << 62),
|
||||
|
||||
Ephemeral = (1ULL << 63),
|
||||
};
|
||||
inline constexpr bool is_flag_type(MessageFlag) { return true; }
|
||||
using MessageFlags = base::flags<MessageFlag>;
|
||||
|
||||
@@ -840,7 +840,9 @@ not_null<HistoryItem*> History::addNewItem(
|
||||
}
|
||||
|
||||
if (!loadedAtBottom() || peer->migrateTo()) {
|
||||
setLastMessage(item);
|
||||
if (!item->isEphemeral()) {
|
||||
setLastMessage(item);
|
||||
}
|
||||
if (unread) {
|
||||
const auto type = item->out()
|
||||
? NewAddType::Outgoing
|
||||
@@ -1214,7 +1216,9 @@ not_null<HistoryItem*> History::addNewToBack(
|
||||
}
|
||||
}
|
||||
|
||||
setLastMessage(item);
|
||||
if (!item->isEphemeral()) {
|
||||
setLastMessage(item);
|
||||
}
|
||||
if (unread) {
|
||||
const auto type = item->out()
|
||||
? NewAddType::Outgoing
|
||||
|
||||
@@ -345,6 +345,9 @@ public:
|
||||
[[nodiscard]] bool isLocal() const {
|
||||
return _flags & MessageFlag::Local;
|
||||
}
|
||||
[[nodiscard]] bool isEphemeral() const {
|
||||
return _flags & MessageFlag::Ephemeral;
|
||||
}
|
||||
[[nodiscard]] bool isFakeAboutView() const {
|
||||
return _flags & MessageFlag::FakeAboutView;
|
||||
}
|
||||
|
||||
@@ -648,6 +648,27 @@ bool LookupReplyIsTopicPost(HistoryItem *replyTo) {
|
||||
&& (replyTo->topicRootId() != Data::ForumTopic::kGeneralId);
|
||||
}
|
||||
|
||||
bool ShowEphemeralReplyTextOnlyError(
|
||||
std::shared_ptr<ChatHelpers::Show> show,
|
||||
not_null<Main::Session*> session,
|
||||
FullMsgId replyToId) {
|
||||
const auto item = session->data().message(replyToId);
|
||||
if (!item || !item->isEphemeral()) {
|
||||
return false;
|
||||
}
|
||||
show->showToast(tr::lng_ephemeral_reply_text_only(tr::now));
|
||||
return true;
|
||||
}
|
||||
|
||||
void StripEphemeralReply(
|
||||
not_null<Main::Session*> session,
|
||||
FullReplyTo &replyTo) {
|
||||
const auto item = session->data().message(replyTo.messageId);
|
||||
if (item && item->isEphemeral()) {
|
||||
replyTo.messageId = FullMsgId();
|
||||
}
|
||||
}
|
||||
|
||||
TextWithEntities DropDisallowedCustomEmoji(
|
||||
not_null<PeerData*> to,
|
||||
TextWithEntities text) {
|
||||
|
||||
@@ -128,6 +128,13 @@ void RequestDependentMessageStory(
|
||||
not_null<History*> history,
|
||||
FullReplyTo replyTo);
|
||||
[[nodiscard]] bool LookupReplyIsTopicPost(HistoryItem *replyTo);
|
||||
[[nodiscard]] bool ShowEphemeralReplyTextOnlyError(
|
||||
std::shared_ptr<ChatHelpers::Show> show,
|
||||
not_null<Main::Session*> session,
|
||||
FullMsgId replyToId);
|
||||
void StripEphemeralReply(
|
||||
not_null<Main::Session*> session,
|
||||
FullReplyTo &replyTo);
|
||||
|
||||
struct SendingErrorRequest {
|
||||
MsgId topicRootId = 0;
|
||||
|
||||
@@ -30,6 +30,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "storage/storage_account.h"
|
||||
#include "storage/storage_facade.h"
|
||||
#include "data/components/credits.h"
|
||||
#include "data/components/ephemeral_messages.h"
|
||||
#include "data/components/factchecks.h"
|
||||
#include "data/components/gift_auctions.h"
|
||||
#include "data/components/location_pickers.h"
|
||||
@@ -119,6 +120,7 @@ Session::Session(
|
||||
, _recentSharedGifts(std::make_unique<Data::RecentSharedMediaGifts>(this))
|
||||
, _giftAuctions(std::make_unique<Data::GiftAuctions>(this))
|
||||
, _scheduledMessages(std::make_unique<Data::ScheduledMessages>(this))
|
||||
, _ephemeralMessages(std::make_unique<Data::EphemeralMessages>(this))
|
||||
, _sponsoredMessages(std::make_unique<Data::SponsoredMessages>(this))
|
||||
, _topPeers(std::make_unique<Data::TopPeers>(this, Data::TopPeerType::Chat))
|
||||
, _topBotApps(
|
||||
|
||||
@@ -37,6 +37,7 @@ class RecentInlineBots;
|
||||
class RecentPeers;
|
||||
class RecentSharedMediaGifts;
|
||||
class ScheduledMessages;
|
||||
class EphemeralMessages;
|
||||
class SponsoredMessages;
|
||||
class TopPeers;
|
||||
class Factchecks;
|
||||
@@ -157,6 +158,9 @@ public:
|
||||
[[nodiscard]] Data::ScheduledMessages &scheduledMessages() const {
|
||||
return *_scheduledMessages;
|
||||
}
|
||||
[[nodiscard]] Data::EphemeralMessages &ephemeralMessages() const {
|
||||
return *_ephemeralMessages;
|
||||
}
|
||||
[[nodiscard]] Data::TopPeers &topPeers() const {
|
||||
return *_topPeers;
|
||||
}
|
||||
@@ -323,6 +327,7 @@ private:
|
||||
const std::unique_ptr<Data::RecentSharedMediaGifts> _recentSharedGifts;
|
||||
const std::unique_ptr<Data::GiftAuctions> _giftAuctions;
|
||||
const std::unique_ptr<Data::ScheduledMessages> _scheduledMessages;
|
||||
const std::unique_ptr<Data::EphemeralMessages> _ephemeralMessages;
|
||||
const std::unique_ptr<Data::SponsoredMessages> _sponsoredMessages;
|
||||
const std::unique_ptr<Data::TopPeers> _topPeers;
|
||||
const std::unique_ptr<Data::TopPeers> _topBotApps;
|
||||
|
||||
Reference in New Issue
Block a user