Added ephemeral messages data component with receive and send support.

This commit is contained in:
23rd
2026-06-11 13:21:40 +03:00
committed by John Preston
parent d5141215df
commit c511e828b3
16 changed files with 645 additions and 10 deletions
+2
View File
@@ -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
+6
View File
@@ -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";
+16
View File
@@ -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);
+23 -7
View File
@@ -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;
+1 -1
View File
@@ -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 &,
+2
View File
@@ -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>;
+6 -2
View File
@@ -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(
+5
View File
@@ -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;